Tricky Rails Mailer
How Rails ActionMailer trick methods
- 
    
ActionMailer in Rails, yooo its awesome it do tons of stuff and makes my life easy while it comes to sending email from Rails app.
 - 
    
If you don’t know much about it, you should check ActionMailer guide as well, explained pretty well.
 - 
    
A bulb flashed ;) :-
- I was writing some code to send emails after user signs up:-
 
Model class:-
class User < ActiveRecord::Base after_commit :notify_user, on: :create def notify_user MyAwesomeMailer.send_welcome_email(self, "Welcome to Interakt.co").deliver_now end endMailer class:-
class MyAwesomeMailer < ActionMailer::Base def send_welcome_email(_user, _message) @user = _user @message = _message end end- All of sudden a bulb flashed over my head and I realized 
send_welcome_emailis aninstance methodand I called aclass method here. 
MyAwesomeMailer.send_welcome_email(self, "Welcome to Interakt.co").deliver_now- I was confused and asked from couple of my senior but didn’t get exact answer, one tried to guess and said it might be using 
method_missinghook of Ruby. 
 - 
    
Getting my Hands Dirty:-
- 
        
Finally decided to get my hands dirty with source code for first time and trust me it was worth it :).
 - 
        
Once I cloned the Rails source code I jumped directly into ActionMailer::Base module and started browsing it.
 - 
        
First thing I tried with it was calling
send_welcome_emaillike a instance method but it didn’t work, go and try it you will get the same exception.MyAwesomeMailer.new.send_welcome_email(self, "Welcome to Interakt.co").deliver_now NoMethodError: private method `new' called for MyAwesomeMailer:Class 
 - 
        
 - 
    
Make class method private:-
- 
        
Rails mark new method of mailer as
private methodso you can’t call it using dot(.) operator. - 
        
Another thing that I analyzed was new is a
class methodand if I useprivateorprotectedit will not work for class methods. 
e.g:-
class MyAwesomeClass private # Instance method def my_instance_method puts "I am not public any more" end # Class method def self.my_class_method puts "I am still public method" end end MyAwesomeClass.my_class_method ==> I am still public method- 
        
Callling
MyAwesomeClass.my_class_methodand it will work and its not a private method, it cleary indicates its not set as private method. Now the question is how to make class level methods private. - 
        
I came across a new method
private_class_methodfor ruby, usage:-private_class_method :new 
It makes class methods private and Rails use it all the time.
 - 
        
 - 
    
Getting the idea:-
- 
        
Coming back to our point when I call a class method how it calls the instance method with the same name.
 - 
        
Finally after messing my head around for some time I got the answer and that is its basically using
method_missinghook of ruby and doing the stuff in backend. 
 - 
        
 - 
    
Flow(How it works internally):-
- When you call a class method e.g:-
 
MyAwesomeMailer.send_welcome_email(self, "Welcome to Interakt.co").deliver_now- first thing is that(Class Method) method is not available there so 
method_missingwill be called and this is the code insidemethod_missinghook method. 
class Base < AbstractController::Base class << self 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 end end- In code about that method is not called but created new object of MessageDelivery and yeah you guessed it right that class is doing the whole stuff, have a look on code:-
 
class MessageDelivery < Delegator def initialize(mailer, mail_method, *args) #:nodoc: @mailer = mailer @mail_method = mail_method @args = args end def __getobj__ #:nodoc: @obj ||= @mailer.send(:new, @mail_method, *@args).message end # Returns the Mail::Message object def message __getobj__ end def deliver_now message.deliver end end- Here after initializing the object, when you call 
deliver_nowto deliver your email and it calls message method wich call__getobj__method. 
def __getobj__ #:nodoc: @obj ||= @mailer.send(:new, @mail_method, *@args).message end- Here 
@maileris containing you class i.e MyAwesomeMailer,@mail_methodis containing method_name you called. - This code is basically creating a new instance of your 
MyAwesomeMailerclass and running the method you called on it. 
 
One conclusion of all that is Rails is doing a lot of magic behind the scene and helping developers to focus on business logic. Its very easy to begin with but it take some efforts to master in & out of it :).
Happy Coding :)
Written on May 22, 2015Share in your circles :)