Damon Yuan Damon Yuan - 4 months ago 16
Ruby Question

Why Rails instance method can be used as class method in rspec

I found a snippet in an article about sending mails in a Rails application:

class ExampleMailerPreview < ActionMailer::Preview
def sample_mail_preview
ExampleMailer.sample_email(User.first)
end
end


in this link: http://www.gotealeaf.com/blog/handling-emails-in-rails.

I do not know why the method:
sample_email()
, which in my mind should be an instance method, can be accessed like class method here as
ExampleMailer.sample_email()
. Can anyone explain?

Answer

It's not an rspec thing, it's an ActionMailer thing. Looking at:

https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb

Take a look at the comments in lines 135-146:

# = Sending mail
#
# Once a mailer action and template are defined, you can deliver your message or defer its creation and
# delivery for later:
#
#   NotifierMailer.welcome(User.first).deliver_now # sends the email
#   mail = NotifierMailer.welcome(User.first)      # => an ActionMailer::MessageDelivery object
#   mail.deliver_now                               # generates and sends the email now
#
# The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
# your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>,
# you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.

The functionality is implemented by defining a method_missing method on the ActionMailer::Base class that looks like:

  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

Essentially, defining a method on an ActionMailer instance (NotifierMailer in the comment example) and then calling it on the class creates a new MessageDelivery instance which delegates to a new instance of the ActionMailer class.