ruby-on-railsrubyactionmailer

Rails ActionMailer default-method used in two classes: What's the difference?


From the ActionMailer-Documentation:

# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  # ...
end

... and ...

class UserMailer < ApplicationMailer
  default from: "notifications@example.com"

  # ...
end

The default-method is used two times. The usage in the UserMailer is explained: "The default method sets default values for all emails sent from this mailer."

But what does the default-method in the ApplicationMailer?

Which effect is accomplished by the TWO invocations in different classes in the end?


Solution

  • This is just inheritance but with a little twist from ActiveSupport. The default value of the parent class becomes the default for any subclass.

    class ApplicationMailer < ActionMailer::Base
      default from: "from@example.com"
      # ...
    end
    
    
    class FooMailer < ApplicationMailer 
    end 
    
    sandbox8(dev)> FooMailer.default[:from]
    => "from@example.com"
    

    The default parameters are stored as a class attribute:

    class_attribute :default_params, default: {
      mime_version: "1.0",
      charset:      "UTF-8",
      content_type: "text/plain",
      parts_order:  [ "text/plain", "text/enriched", "text/html" ]
    }.freeze
    

    Class attributes are an ActiveSupport construct that are used somewhat like class variables but avoid the major pitfall of class variables which is that they are shared between a class and it's subclasses. This is something that ActiveSupport builds with the basic building blocks provided by Ruby and isn't inherent to the language.

    What the default method does replace this class attribute with a merger between the existing defaults and the passed hash:

    # Allows to set defaults through app configuration:
    #
    #    config.action_mailer.default_options = { from: "no-reply@example.org" }
    def default(value = nil)
      self.default_params = default_params.merge(value).freeze if value
      default_params
    end
    

    This ensures that you're not modifying the existing object - which would effect subclasses.

    So when you do:

    class UserMailer < ApplicationMailer
      default from: "notifications@example.com"
    
      # ...
    end
    

    You're changing the default values for instances of UserMailer. But other descendant classes of ApplicationMailer remain unchanged.