How can you call class methods on mailers when they're not defined as such?
Asked Answered
C

2

12

When sending mail in Rails, usually one would do something like this:

UserMailer.password_reset(user).deliver

But if we look inside UserMailer we can see this:

def password_reset(user) # not self.password_reset
  # ...
end

Notice that the method name is not prefixed with self. Looking at it, it seems like you need to instantiate the object first as below. How does Rails do this?

UserMailer.new.password_reset(user).deliver
Celerity answered 13/6, 2013 at 13:41 Comment(0)
P
12

That's a great question. In the source (https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb), Rails uses method_missing to create a new instance of the ActionMailer. Here's the relevant excerpt from the source:

def method_missing(method_name, *args) # :nodoc:
  if respond_to?(method_name)
    new(method_name, *args).message
  else
    super
  end
end
Peachy answered 13/6, 2013 at 14:46 Comment(5)
Thank you. But why not just make it a class method? What do we gain from this approach?Celerity
I'm not sure if I understand entirely. Do you mean why not make it an instance method? So, like UserMailer.new.password_reset(user).deliver?Peachy
I mean why use method missing? Keep it UserMailer.password_reset(user).deliver but change def method to def self.methodCelerity
deliver has been defined as a class method in the ActionMailer module, see my answer.Lagoon
I don't necessarily understand all the design decisions here, but if you inspect the source, there's a lot going on behind the scenes. It seems that they are creating new instances of the mailer, and they need method_missing to do that, since your class method doesn't do that. I think the best thing would be to dig into the source yourself. Hope that helps.Peachy
L
0

Please note that the self.deliver method is deprecated, have a look at the code given below. Since deliver method is defined as a class method you don't have to instantiate the mailer class.

action_mailer/deprecated_api.rb

module ActionMailer
  module DeprecatedApi #:nodoc:
  extend ActiveSupport::Concern

  module ClassMethods
  # Deliver the given mail object directly. This can be used to deliver
  # a preconstructed mail object, like:
  #
  #   email = MyMailer.create_some_mail(parameters)
  #   email.set_some_obscure_header "frobnicate"
  #   MyMailer.deliver(email)
  def deliver(mail, show_warning=true)
    if show_warning
      ActiveSupport::Deprecation.warn "#{self}.deliver is deprecated, call " <<
        "deliver in the mailer instance instead", caller[0,2]
    end

    raise "no mail object available for delivery!" unless mail
    wrap_delivery_behavior(mail)
    mail.deliver
    mail
  end

Sending mail

Once a mailer action and template are defined, you can deliver your message or create it and save it for delivery later:

mail = Notifier.welcome(david)  # => a Mail::Message object
mail.deliver  # sends the email

#Above 2 steps can be combined
Notifier.welcome(david).deliver

You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.

The method password_reset returns a Mail::Message object which can then just be told deliver to send itself out.

Lagoon answered 13/6, 2013 at 16:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.