Have you ever wondered how it is possible that calling class methods on mailers work in Rails, even though you only define some instance methods in those classes? It seems like it’s quite common question, especially when you see the mailers in action for the first time. Apparently, there is some Ruby "magic" involved here, so let’s try to decode it and check what happens under the hood.
Anatomy of ActionMailer mailers
Let’s start with adding a very simple
WelcomeMailer as an example:
# app/mailers/welcome_mailer.rb class WelcomeMailer < ApplicationMailer def welcome(user) @user = user mail to: user.email end end
If we wanted to send
welcome email to some
user, we would write the following code:
It’s quite interesting that it works just like that, even though we have never defined any class method in
WelcomeMailer. Most likely it’s handled with
method_missing magic in
ActionMailer::Base. To verify that, let’s dive into Rails source code. In ActionMailer::Base class we indeed have
method_missing defined for class methods:
def method_missing(method_name, *args) # :nodoc: if action_methods.include?(method_name.to_s) MessageDelivery.new(self, method_name, *args) else super end end
Basically, any action method defined in mailer class will be intercepted by
method_missing and will return an instance of
MessageDelivery, otherwise it runs the default implementation. And where do
action methods come from?
ActionMailer::Base inherits from
AbstractController::Base, so it works exactly the same as for controllers - it returns a set of public instance methods of a given class.
We now have a better idea what actually happens when we call class methods on mailers, but it still doesn’t answer the questions: how is the mailer instantiated and how is the instance method called on it? To investigate it further, we need to check MessageDelivery class. We are particularly interested in deliver_now method (could be any other delivery method, but let’s stick to this single one) with the following body:
def deliver_now processed_mailer.handle_exceptions do message.deliver end end
Looks like processed_mailer is the key method that we were looking for:
def processed_mailer @processed_mailer ||= @mailer_class.new.tap do |mailer| mailer.process @action, *@args end end
This method creates the instance of the mailer, calls
process method with
@action argument (which is the name of the instance method) and with
@args, which are the arguments passed to the class method and in the end it returns the created instance of the mailer. Inside
deliver method is called. And where does this one come from?
MessageDelivery inherits from
Delegator class and delegates all the method calls for methods not implemented by
processed_mailer.message, which is the attribute defined in our mailer instance itself.
And that’s it! It took a bit switching between different methods and classes to understand the entire flow and what happens under the hood, but it’s clear that such interface hiding all the complexity is quite convenient.
Some parts of Rails may contain a lot of "magic" which makes understanding the details more difficult. However, thanks to that magic, the usage of these parts is greatly simplified by the nice abstraction and easy to use interface, which is exactly the case with Rails mailers.