aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailer/lib/action_mailer/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionmailer/lib/action_mailer/base.rb')
-rw-r--r--actionmailer/lib/action_mailer/base.rb397
1 files changed, 259 insertions, 138 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 2207f119c3..bb3cb1be45 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -15,11 +15,17 @@ module ActionMailer
#
# $ rails generate mailer Notifier
#
- # The generated model inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
+ # The generated model inherits from <tt>ApplicationMailer</tt> which in turn
+ # inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
# used to generate an email message. In these methods, you can setup variables to be used in
# the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
#
- # class Notifier < ActionMailer::Base
+ # class ApplicationMailer < ActionMailer::Base
+ # default from: 'from@example.com'
+ # layout 'mailer'
+ # end
+ #
+ # class NotifierMailer < ApplicationMailer
# default from: 'no-reply@example.com',
# return_path: 'system@example.com'
#
@@ -39,23 +45,20 @@ module ActionMailer
# in the same manner as <tt>attachments[]=</tt>
#
# * <tt>headers[]=</tt> - Allows you to specify any header field in your email such
- # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields like <tt>To:</tt>
- # <tt>From:</tt> can only appear once in an email header, other fields like <tt>X-Anything</tt>
- # can appear multiple times. If you want to change a field that can appear multiple times,
- # you need to set it to nil first so that Mail knows you are replacing it and not adding
- # another field of the same name.
+ # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note that declaring a header multiple times
+ # will add many fields of the same name. Read #headers doc for more information.
#
# * <tt>headers(hash)</tt> - Allows you to specify multiple headers in your email such
# as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt>
#
# * <tt>mail</tt> - Allows you to specify email to be sent.
#
- # The hash passed to the mail method allows you to specify any header that a Mail::Message
- # will accept (any valid Email header including optional fields).
+ # The hash passed to the mail method allows you to specify any header that a <tt>Mail::Message</tt>
+ # will accept (any valid email header including optional fields).
#
# The mail method, if not passed a block, will inspect your views and send all the views with
# the same name as the method, so the above action would send the +welcome.text.erb+ view
- # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email.
+ # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email.
#
# If you want to explicitly render only certain templates, pass a block:
#
@@ -85,16 +88,17 @@ module ActionMailer
#
# To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
# name as the method in your mailer model. For example, in the mailer defined above, the template at
- # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email.
+ # <tt>app/views/notifier_mailer/welcome.text.erb</tt> would be used to generate the email.
#
- # Variables defined in the model are accessible as instance variables in the view.
+ # Variables defined in the methods of your mailer model are accessible as instance variables in their
+ # corresponding view.
#
# Emails by default are sent in plain text, so a sample view for our model example might look like this:
#
# Hi <%= @account.name %>,
# Thanks for joining our service! Please check back often.
#
- # You can even use Action Pack helpers in these views. For example:
+ # You can even use Action View helpers in these views. For example:
#
# You got a new note!
# <%= truncate(@note.body, length: 25) %>
@@ -128,21 +132,32 @@ module ActionMailer
#
# config.action_mailer.default_url_options = { host: "example.com" }
#
- # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
- # <tt>only_path: false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
- # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
- # <tt>only_path: false</tt> will ensure that absolute URLs are generated.
+ # By default when <tt>config.force_ssl</tt> is true, URLs generated for hosts will use the HTTPS protocol.
#
# = Sending mail
#
- # Once a mailer action and template are defined, you can deliver your message or create it and save it
- # for delivery later:
+ # 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.
#
- # Notifier.welcome(david).deliver # sends the email
- # mail = Notifier.welcome(david) # => a Mail::Message object
- # mail.deliver # sends the email
+ # NotifierMailer.welcome(User.first).message # => a Mail::Message object
+ #
+ # Action Mailer is nicely integrated with Active Job so you can generate and send emails in the background
+ # (example: outside of the request-response cycle, so the user doesn't have to wait on it):
+ #
+ # NotifierMailer.welcome(User.first).deliver_later # enqueue the email sending to Active Job
+ #
+ # Note that <tt>deliver_later</tt> will execute your method from the background job.
#
# You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
+ # All instance methods are expected to return a message object to be sent.
#
# = Multipart Emails
#
@@ -154,7 +169,7 @@ module ActionMailer
# * signup_notification.text.erb
# * signup_notification.html.erb
# * signup_notification.xml.builder
- # * signup_notification.yaml.erb
+ # * signup_notification.yml.erb
#
# Each would be rendered and added as a separate part to the message, with the corresponding content
# type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
@@ -169,7 +184,7 @@ module ActionMailer
#
# Sending attachment in emails is easy:
#
- # class ApplicationMailer < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# def welcome(recipient)
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
# mail(to: recipient, subject: "New account information")
@@ -185,7 +200,7 @@ module ActionMailer
# If you need to send attachments with no content, you need to create an empty view for it,
# or add an empty body parameter like this:
#
- # class ApplicationMailer < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# def welcome(recipient)
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
# mail(to: recipient, subject: "New account information", body: "")
@@ -197,7 +212,7 @@ module ActionMailer
# You can also specify that a file should be displayed inline with other HTML. This is useful
# if you want to display a corporate logo or a photo.
#
- # class ApplicationMailer < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# def welcome(recipient)
# attachments.inline['photo.png'] = File.read('path/to/photo.png')
# mail(to: recipient, subject: "Here is what we look like")
@@ -229,14 +244,14 @@ module ActionMailer
# An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be
# called before the email is sent, allowing you to make modifications to the email before it hits
# the delivery agents. Your class should make any needed modifications directly to the passed
- # in Mail::Message instance.
+ # in <tt>Mail::Message</tt> instance.
#
# = Default Hash
#
# Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
# default method inside the class definition:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# default sender: 'system@example.com'
# end
#
@@ -244,8 +259,8 @@ module ActionMailer
# <tt>ActionMailer::Base</tt> sets the following:
#
# * <tt>mime_version: "1.0"</tt>
- # * <tt>charset: "UTF-8",</tt>
- # * <tt>content_type: "text/plain",</tt>
+ # * <tt>charset: "UTF-8"</tt>
+ # * <tt>content_type: "text/plain"</tt>
# * <tt>parts_order: [ "text/plain", "text/enriched", "text/html" ]</tt>
#
# <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
@@ -254,7 +269,7 @@ module ActionMailer
# As you can pass in any header, you need to either quote the header as a string, or pass it in as
# an underscored symbol, so the following will work:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# default 'Content-Transfer-Encoding' => '7bit',
# content_description: 'This is a description'
# end
@@ -262,7 +277,7 @@ module ActionMailer
# Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
# can define methods that evaluate as the message is being generated:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# default 'X-Special-Header' => Proc.new { my_method }
#
# private
@@ -273,7 +288,7 @@ module ActionMailer
# end
#
# Note that the proc is evaluated right at the start of the mail message generation, so if you
- # set something in the defaults using a proc, and then set the same thing inside of your
+ # set something in the default using a proc, and then set the same thing inside of your
# mailer method, it will get over written by the mailer method.
#
# It is also possible to set these default options that will be used in all mailers through
@@ -287,7 +302,7 @@ module ActionMailer
# This may be useful, for example, when you want to add default inline attachments for all
# messages sent out by a certain mailer class:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# before_action :add_inline_attachment!
#
# def welcome
@@ -301,12 +316,51 @@ module ActionMailer
# end
# end
#
- # Callbacks in ActionMailer are implemented using AbstractController::Callbacks, so you
- # can define and configure callbacks in the same manner that you would use callbacks in
- # classes that inherit from ActionController::Base.
+ # Callbacks in Action Mailer are implemented using
+ # <tt>AbstractController::Callbacks</tt>, so you can define and configure
+ # callbacks in the same manner that you would use callbacks in classes that
+ # inherit from <tt>ActionController::Base</tt>.
+ #
+ # Note that unless you have a specific reason to do so, you should prefer
+ # using <tt>before_action</tt> rather than <tt>after_action</tt> in your
+ # Action Mailer classes so that headers are parsed properly.
+ #
+ # = Previewing emails
+ #
+ # You can preview your email templates visually by adding a mailer preview file to the
+ # <tt>ActionMailer::Base.preview_path</tt>. Since most emails do something interesting
+ # with database data, you'll need to write some scenarios to load messages with fake data:
+ #
+ # class NotifierMailerPreview < ActionMailer::Preview
+ # def welcome
+ # NotifierMailer.welcome(User.first)
+ # end
+ # end
#
- # Note that unless you have a specific reason to do so, you should prefer using before_action
- # rather than after_action in your ActionMailer classes so that headers are parsed properly.
+ # Methods must return a <tt>Mail::Message</tt> object which can be generated by calling the mailer
+ # method without the additional <tt>deliver_now</tt> / <tt>deliver_later</tt>. The location of the
+ # mailer previews directory can be configured using the <tt>preview_path</tt> option which has a default
+ # of <tt>test/mailers/previews</tt>:
+ #
+ # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
+ #
+ # An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
+ # on a running development server instance.
+ #
+ # Previews can also be intercepted in a similar manner as deliveries can be by registering
+ # a preview interceptor that has a <tt>previewing_email</tt> method:
+ #
+ # class CssInlineStyler
+ # def self.previewing_email(message)
+ # # inline CSS styles
+ # end
+ # end
+ #
+ # config.action_mailer.preview_interceptors :css_inline_styler
+ #
+ # Note that interceptors need to be registered both with <tt>register_interceptor</tt>
+ # and <tt>register_preview_interceptor</tt> if they should operate on both sending and
+ # previewing emails.
#
# = Configuration options
#
@@ -317,7 +371,7 @@ module ActionMailer
# per the above section.
#
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
- # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
+ # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers.
#
# * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
# * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default
@@ -328,15 +382,16 @@ module ActionMailer
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the
# authentication type here.
- # This is a symbol and one of <tt>:plain</tt> (will send the password in the clear), <tt>:login</tt> (will
- # send password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
+ # This is a symbol and one of <tt>:plain</tt> (will send the password Base64 encoded), <tt>:login</tt> (will
+ # send the password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
# information and a cryptographic Message Digest 5 algorithm to hash important information)
- # * <tt>:enable_starttls_auto</tt> - When set to true, detects if STARTTLS is enabled in your SMTP server
- # and starts to use it.
+ # * <tt>:enable_starttls_auto</tt> - Detects if STARTTLS is enabled in your SMTP server and starts
+ # to use it. Defaults to <tt>true</tt>.
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
- # of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the
- # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER, ...).
+ # of an OpenSSL verify constant (<tt>'none'</tt>, <tt>'peer'</tt>, <tt>'client_once'</tt>,
+ # <tt>'fail_if_no_peer_cert'</tt>) or directly the constant (<tt>OpenSSL::SSL::VERIFY_NONE</tt>,
+ # <tt>OpenSSL::SSL::VERIFY_PEER</tt>, ...).
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
@@ -351,17 +406,20 @@ module ActionMailer
#
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default),
# <tt>:sendmail</tt>, <tt>:test</tt>, and <tt>:file</tt>. Or you may provide a custom delivery method
- # object e.g. MyOwnDeliveryMethodClass. See the Mail gem documentation on the interface you need to
+ # object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to
# implement for a custom delivery agent.
#
# * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
- # call <tt>.deliver</tt> on an mail message or on an Action Mailer method. This is on by default but can
+ # call <tt>.deliver</tt> on an email message or on an Action Mailer method. This is on by default but can
# be turned off to aid in functional testing.
#
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
+ #
+ # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>.
class Base < AbstractController::Base
include DeliveryMethods
+ include Previews
abstract!
@@ -373,6 +431,8 @@ module ActionMailer
include AbstractController::AssetPaths
include AbstractController::Callbacks
+ include ActionView::Layouts
+
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
def _protected_ivars # :nodoc:
@@ -381,8 +441,6 @@ module ActionMailer
helper ActionMailer::MailHelper
- private_class_method :new #:nodoc:
-
class_attribute :default_params
self.default_params = {
mime_version: "1.0",
@@ -403,20 +461,28 @@ module ActionMailer
end
# Register an Observer which will be notified when mail is delivered.
- # Either a class or a string can be passed in as the Observer. If a string is passed in
- # it will be +constantize+d.
+ # Either a class, string or symbol can be passed in as the Observer.
+ # If a string or symbol is passed in it will be camelized and constantized.
def register_observer(observer)
- delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
- Mail.register_observer(delivery_observer)
+ Mail.register_observer(observer_class_for(observer))
end
# Register an Interceptor which will be called before mail is sent.
- # Either a class or a string can be passed in as the Interceptor. If a string is passed in
- # it will be <tt>constantize</tt>d.
+ # Either a class, string or symbol can be passed in as the Interceptor.
+ # If a string or symbol is passed in it will be camelized and constantized.
def register_interceptor(interceptor)
- delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
- Mail.register_interceptor(delivery_interceptor)
+ Mail.register_interceptor(observer_class_for(interceptor))
+ end
+
+ def observer_class_for(value) # :nodoc:
+ case value
+ when String, Symbol
+ value.to_s.camelize.constantize
+ else
+ value
+ end
end
+ private :observer_class_for
# Returns the name of current mailer. This method is also being used as a path for a view lookup.
# If this is an anonymous mailer, this method will return +anonymous+ instead.
@@ -429,7 +495,7 @@ module ActionMailer
# Sets the defaults through app configuration:
#
- # config.action_mailer.default { from: "no-reply@example.org" }
+ # config.action_mailer.default(from: "no-reply@example.org")
#
# Aliased by ::default_options=
def default(value = nil)
@@ -462,11 +528,11 @@ module ActionMailer
end
end
- # Wraps an email delivery inside of ActiveSupport::Notifications instrumentation.
+ # Wraps an email delivery inside of <tt>ActiveSupport::Notifications</tt> instrumentation.
#
- # This method is actually called by the Mail::Message object itself
- # through a callback when you call +:deliver+ on the Mail::Message,
- # calling +deliver_mail+ directly and passing a Mail::Message will do
+ # This method is actually called by the <tt>Mail::Message</tt> object itself
+ # through a callback when you call <tt>:deliver</tt> on the <tt>Mail::Message</tt>,
+ # calling +deliver_mail+ directly and passing a <tt>Mail::Message</tt> will do
# nothing except tell the logger you sent the email.
def deliver_mail(mail) #:nodoc:
ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
@@ -475,10 +541,6 @@ module ActionMailer
end
end
- def respond_to?(method, include_private = false) #:nodoc:
- super || action_methods.include?(method.to_s)
- end
-
protected
def set_payload_for_mail(payload, mail) #:nodoc:
@@ -494,12 +556,18 @@ module ActionMailer
end
def method_missing(method_name, *args) # :nodoc:
- if respond_to?(method_name)
- new(method_name, *args).message
+ if action_methods.include?(method_name.to_s)
+ MessageDelivery.new(self, method_name, *args)
else
super
end
end
+
+ private
+
+ def respond_to_missing?(method, include_all = false) #:nodoc:
+ action_methods.include?(method.to_s)
+ end
end
attr_internal :message
@@ -508,22 +576,19 @@ module ActionMailer
# will be initialized according to the named method. If not, the mailer will
# remain uninitialized (useful when you only need to invoke the "receive"
# method, for instance).
- def initialize(method_name=nil, *args)
+ def initialize
super()
@_mail_was_called = false
@_message = Mail.new
- process(method_name, *args) if method_name
end
def process(method_name, *args) #:nodoc:
payload = {
- :mailer => self.class.name,
- :action => method_name
+ mailer: self.class.name,
+ action: method_name
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
- lookup_context.skip_default_locale!
-
super
@_message = NullMail.new unless @_mail_was_called
end
@@ -531,6 +596,11 @@ module ActionMailer
class NullMail #:nodoc:
def body; '' end
+ def header; {} end
+
+ def respond_to?(string, include_all=false)
+ true
+ end
def method_missing(*args)
nil
@@ -542,20 +612,40 @@ module ActionMailer
self.class.mailer_name
end
- # Allows you to pass random and unusual headers to the new Mail::Message
+ # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt>
# object which will add them to itself.
#
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
#
# You can also pass a hash into headers of header field names and values,
- # which will then be set on the Mail::Message object:
+ # which will then be set on the <tt>Mail::Message</tt> object:
#
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
# 'In-Reply-To' => incoming.message_id
#
- # The resulting Mail::Message will have the following in its header:
+ # The resulting <tt>Mail::Message</tt> will have the following in its header:
#
# X-Special-Domain-Specific-Header: SecretValue
+ #
+ # Note about replacing already defined headers:
+ #
+ # * +subject+
+ # * +sender+
+ # * +from+
+ # * +to+
+ # * +cc+
+ # * +bcc+
+ # * +reply-to+
+ # * +orig-date+
+ # * +message-id+
+ # * +references+
+ #
+ # Fields can only appear once in email headers while other fields such as
+ # <tt>X-Anything</tt> can appear multiple times.
+ #
+ # If you want to replace any header which already exists, first set it to
+ # +nil+ in order to reset the value otherwise another field will be added
+ # for the same header.
def headers(args = nil)
if args
@_message.headers(args)
@@ -568,18 +658,18 @@ module ActionMailer
#
# mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
#
- # If you do this, then Mail will take the file name and work out the mime type
- # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and
- # base64 encode the contents of the attachment all for you.
+ # If you do this, then Mail will take the file name and work out the mime type.
+ # It will also set the Content-Type, Content-Disposition, Content-Transfer-Encoding
+ # and encode the contents of the attachment in Base64.
#
# You can also specify overrides if you want by passing a hash instead of a string:
#
# mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
# content: File.read('/path/to/filename.jpg')}
#
- # If you want to use a different encoding than Base64, you can pass an encoding in,
- # but then it is up to you to pass in the content pre-encoded, and don't expect
- # Mail to know how to decode this data:
+ # If you want to use encoding other than Base64 then you will need to pass encoding
+ # type along with the pre-encoded content as Mail doesn't know how to decode the
+ # data:
#
# file_content = SpecialEncode(File.read('/path/to/filename.jpg'))
# mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
@@ -595,14 +685,29 @@ module ActionMailer
# mail.attachments[0] # => Mail::Part (first attachment)
#
def attachments
- @_message.attachments
+ if @_mail_was_called
+ LateAttachmentsProxy.new(@_message.attachments)
+ else
+ @_message.attachments
+ end
+ end
+
+ class LateAttachmentsProxy < SimpleDelegator
+ def inline; _raise_error end
+ def []=(_name, _content); _raise_error end
+
+ private
+ def _raise_error
+ raise RuntimeError, "Can't add attachments after `mail` was called.\n" \
+ "Make sure to use `attachments[]=` before calling `mail`."
+ end
end
# The main method that creates the message and renders the email templates. There are
# two ways to call this method, with a block, or without a block.
#
- # Both methods accept a headers hash. This hash allows you to specify the most used headers
- # in an email message, these are:
+ # It accepts a headers hash. This hash allows you to specify
+ # the most used headers in an email message, these are:
#
# * +:subject+ - The subject of the message, if this is omitted, Action Mailer will
# ask the Rails I18n class for a translated +:subject+ in the scope of
@@ -642,13 +747,13 @@ module ActionMailer
# templates in the view paths using by default the mailer name and the
# method name that it is being called from, it will then create parts for
# each of these templates intelligently, making educated guesses on correct
- # content type and sequence, and return a fully prepared Mail::Message
- # ready to call +:deliver+ on to send.
+ # content type and sequence, and return a fully prepared <tt>Mail::Message</tt>
+ # ready to call <tt>:deliver</tt> on to send.
#
# For example:
#
# class Notifier < ActionMailer::Base
- # default from: 'no-reply@test.lindsaar.net',
+ # default from: 'no-reply@test.lindsaar.net'
#
# def welcome
# mail(to: 'mikel@test.lindsaar.net')
@@ -671,11 +776,11 @@ module ActionMailer
# format.html
# end
#
- # You can even render text directly without using a template:
+ # You can even render plain text directly without using a template:
#
# mail(to: 'mikel@test.lindsaar.net') do |format|
- # format.text { render text: "Hello Mikel!" }
- # format.html { render text: "<h1>Hello Mikel!</h1>" }
+ # format.text { render plain: "Hello Mikel!" }
+ # format.html { render html: "<h1>Hello Mikel!</h1>".html_safe }
# end
#
# Which will render a +multipart/alternative+ email with +text/plain+ and
@@ -689,49 +794,40 @@ module ActionMailer
# end
#
def mail(headers = {}, &block)
- @_mail_was_called = true
- m = @_message
+ return message if @_mail_was_called && headers.blank? && !block
# At the beginning, do not consider class default for content_type
content_type = headers[:content_type]
- # Call all the procs (if any)
- default_values = {}
- self.class.default.each do |k,v|
- default_values[k] = v.is_a?(Proc) ? instance_eval(&v) : v
- end
-
- # Handle defaults
- headers = headers.reverse_merge(default_values)
- headers[:subject] ||= default_i18n_subject
+ headers = apply_defaults(headers)
# Apply charset at the beginning so all fields are properly quoted
- m.charset = charset = headers[:charset]
+ message.charset = charset = headers[:charset]
# Set configure delivery behavior
- wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:delivery_method_options))
+ wrap_delivery_behavior!(headers[:delivery_method], headers[:delivery_method_options])
- # Assign all headers except parts_order, content_type and body
- assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
- assignable.each { |k, v| m[k] = v }
+ assign_headers_to_message(message, headers)
# Render the templates and blocks
responses = collect_responses(headers, &block)
- create_parts_from_responses(m, responses)
+ @_mail_was_called = true
+
+ create_parts_from_responses(message, responses)
# Setup content type, reapply charset and handle parts order
- m.content_type = set_content_type(m, content_type, headers[:content_type])
- m.charset = charset
+ message.content_type = set_content_type(message, content_type, headers[:content_type])
+ message.charset = charset
- if m.multipart?
- m.body.set_sort_order(headers[:parts_order])
- m.body.sort_parts!
+ if message.multipart?
+ message.body.set_sort_order(headers[:parts_order])
+ message.body.sort_parts!
end
- m
+ message
end
- protected
+ protected
# Used by #mail to set the content type of the message.
#
@@ -748,7 +844,7 @@ module ActionMailer
when user_content_type.present?
user_content_type
when m.has_attachments?
- if m.attachments.detect { |a| a.inline? }
+ if m.attachments.detect(&:inline?)
["multipart", "related", params]
else
["multipart", "mixed", params]
@@ -769,45 +865,70 @@ module ActionMailer
I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize))
end
- def collect_responses(headers) #:nodoc:
- responses = []
+ # Emails do not support relative path links.
+ def self.supports_path?
+ false
+ end
+
+ private
+
+ def apply_defaults(headers)
+ default_values = self.class.default.map do |key, value|
+ [
+ key,
+ value.is_a?(Proc) ? instance_eval(&value) : value
+ ]
+ end.to_h
+
+ headers_with_defaults = headers.reverse_merge(default_values)
+ headers_with_defaults[:subject] ||= default_i18n_subject
+ headers_with_defaults
+ end
+
+ def assign_headers_to_message(message, headers)
+ assignable = headers.except(:parts_order, :content_type, :body, :template_name,
+ :template_path, :delivery_method, :delivery_method_options)
+ assignable.each { |k, v| message[k] = v }
+ end
+ def collect_responses(headers)
if block_given?
collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
yield(collector)
- responses = collector.responses
+ collector.responses
elsif headers[:body]
- responses << {
+ [{
body: headers.delete(:body),
content_type: self.class.default[:content_type] || "text/plain"
- }
+ }]
else
- templates_path = headers.delete(:template_path) || self.class.mailer_name
- templates_name = headers.delete(:template_name) || action_name
+ collect_responses_from_templates(headers)
+ end
+ end
- each_template(Array(templates_path), templates_name) do |template|
- self.formats = template.formats
+ def collect_responses_from_templates(headers)
+ templates_path = headers[:template_path] || self.class.mailer_name
+ templates_name = headers[:template_name] || action_name
- responses << {
- body: render(template: template),
- content_type: template.type.to_s
- }
- end
+ each_template(Array(templates_path), templates_name).map do |template|
+ self.formats = template.formats
+ {
+ body: render(template: template),
+ content_type: template.type.to_s
+ }
end
-
- responses
end
- def each_template(paths, name, &block) #:nodoc:
+ def each_template(paths, name, &block)
templates = lookup_context.find_all(name, paths)
if templates.empty?
raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer')
else
- templates.uniq { |t| t.formats }.each(&block)
+ templates.uniq(&:formats).each(&block)
end
end
- def create_parts_from_responses(m, responses) #:nodoc:
+ def create_parts_from_responses(m, responses)
if responses.size == 1 && !m.has_attachments?
responses[0].each { |k,v| m[k] = v }
elsif responses.size > 1 && m.has_attachments?
@@ -820,7 +941,7 @@ module ActionMailer
end
end
- def insert_part(container, response, charset) #:nodoc:
+ def insert_part(container, response, charset)
response[:charset] ||= charset
part = Mail::Part.new(response)
container.add_part(part)