“Yeah It’s magic.”
When I worked with an Action Mailer, I realized that I called a class method of the Action mailer without defining any class method at the Action mailer. It made me so confused and curious about:
- How can I call the class method without defining them at the mailer?
Action Mailer example
Here is a normal way to create an action mailer.
1
2
3
4
5
6
7
8
9
class UserNotifierMailer < ApplicationMailer
  default :from => "any_from_address@example.com"
  def send_signup_email(user)
    @user = user
    mail( :to => @user.email,
    :subject => "Thanks for signing up for our amazing app" )
  end
end
Now in the controller, add a call to UserNotifierMailer.send_signup_email when a user is saved.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class UsersController < ApplicationController
  def create
    # Create the user from params
    @user = User.new(user_params)
    if @user.save
      # Deliver the signup email
      UserNotifierMailer.send_signup_email(@user).deliver
      redirect_to(@user, :notice => 'User created')
    else
      render :action => "new"
    end
  end
  private
  def user_params
    params.require(:user).permit(:name, :email, :login)
  end
end
Now, Look at line 45 - UserNotifierMailer.send_signup_email(@user).deliver. it is a way to call a class method.
but we created an instance method def send_signup_email  rather than a class method with  self prefix.
Ruby: class methods vs. instance methods
This is how normal class and instance methods would work.
1
2
3
4
5
6
7
8
9
class SayHello
  def self.from_the_class
    "Hello, from a class method"
  end
  def from_an_instance
    "Hello, from an instance method"
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
>> SayHello.from_the_class
=> "Hello, from a class method"
>> SayHello.from_an_instance
=> undefined method `from_an_instance' for SayHello:Class
>> hello = SayHello.new
>> hello.from_the_class
=> undefined method `from_the_class' for #<SayHello:0x0000557920dac930>
>> hello.from_an_instance
=> "Hello, from an instance method"
Then, how action methods in the ActionMailer work?
Decoding Rails Magic: How Does Calling Class Methods On Mailers Work
let’s dive into Rails source code. In ActionMailer::Base class we indeed have method_missing defined for class methods:
1
2
3
4
5
6
7
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.
References
- https://karolgalanciak.com/blog/2016/07/31/decoding-rails-magic-how-does-calling-class-methods-on-mailers-work/
- https://dev.to/adamlombard/ruby-class-methods-vs-instance-methods-4aje
