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:

WelcomeMailer.welcome(user).deliver_now

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 handle_exceptions the 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 MessageDelivery to 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.

Wrapping up

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.

posted in: Ruby, Ruby on Rails, Design Patterns, Architecture