“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