aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailer/lib/action_mailer
diff options
context:
space:
mode:
Diffstat (limited to 'actionmailer/lib/action_mailer')
-rw-r--r--actionmailer/lib/action_mailer/base.rb146
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb15
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb9
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb2
-rw-r--r--actionmailer/lib/action_mailer/preview.rb104
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb10
-rw-r--r--actionmailer/lib/action_mailer/version.rb11
8 files changed, 251 insertions, 48 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index fcdd6747b8..652ce0b3d8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -3,6 +3,7 @@ require 'action_mailer/collector'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
+
require 'action_mailer/log_subscriber'
module ActionMailer
@@ -49,8 +50,8 @@ module ActionMailer
#
# * <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
@@ -93,7 +94,7 @@ module ActionMailer
# 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) %>
@@ -153,7 +154,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>,
@@ -228,7 +229,7 @@ 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
#
@@ -307,6 +308,43 @@ module ActionMailer
# 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.
#
+ # = 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 NotifierPreview < ActionMailer::Preview
+ # def welcome
+ # Notifier.welcome(User.first)
+ # end
+ # end
+ #
+ # Methods must return a <tt>Mail::Message</tt> object which can be generated by calling the mailer
+ # method without the additional <tt>deliver</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.register_preview_interceptor :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
#
# These options are specified on the class level, like
@@ -316,7 +354,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
@@ -334,8 +372,9 @@ module ActionMailer
# and starts to use it.
# * <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>.
@@ -350,7 +389,7 @@ 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
@@ -361,17 +400,25 @@ module ActionMailer
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
class Base < AbstractController::Base
include DeliveryMethods
+ include Previews
+
abstract!
- include AbstractController::Logger
include AbstractController::Rendering
- include AbstractController::Layouts
+
+ include AbstractController::Logger
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::AssetPaths
include AbstractController::Callbacks
- self.protected_instance_variables = [:@_action_has_layout]
+ include ActionView::Layouts
+
+ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
+
+ def _protected_ivars # :nodoc:
+ PROTECTED_IVARS
+ end
helper ActionMailer::MailHelper
@@ -397,18 +444,30 @@ 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)
+ delivery_observer = case observer
+ when String, Symbol
+ observer.to_s.camelize.constantize
+ else
+ observer
+ end
+
Mail.register_observer(delivery_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)
+ delivery_interceptor = case interceptor
+ when String, Symbol
+ interceptor.to_s.camelize.constantize
+ else
+ interceptor
+ end
+
Mail.register_interceptor(delivery_interceptor)
end
@@ -456,11 +515,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|
@@ -509,11 +568,18 @@ module ActionMailer
process(method_name, *args) if method_name
end
- def process(*args) #:nodoc:
- lookup_context.skip_default_locale!
+ def process(method_name, *args) #:nodoc:
+ payload = {
+ mailer: self.class.name,
+ action: method_name
+ }
- super
- @_message = NullMail.new unless @_mail_was_called
+ ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
+ lookup_context.skip_default_locale!
+
+ super
+ @_message = NullMail.new unless @_mail_was_called
+ end
end
class NullMail #:nodoc:
@@ -529,18 +595,18 @@ 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
def headers(args = nil)
@@ -629,13 +695,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')
@@ -658,11 +724,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
@@ -676,6 +742,8 @@ module ActionMailer
# end
#
def mail(headers = {}, &block)
+ return @_message if @_mail_was_called && headers.blank? && !block
+
@_mail_was_called = true
m = @_message
@@ -683,9 +751,9 @@ module ActionMailer
content_type = headers[:content_type]
# Call all the procs (if any)
- class_default = self.class.default
- default_values = class_default.merge(class_default) do |k,v|
- v.respond_to?(:to_proc) ? instance_eval(&v) : v
+ default_values = {}
+ self.class.default.each do |k,v|
+ default_values[k] = v.is_a?(Proc) ? instance_eval(&v) : v
end
# Handle defaults
@@ -696,7 +764,7 @@ module ActionMailer
m.charset = charset = headers[:charset]
# Set configure delivery behavior
- wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:delivery_method_options))
+ wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(: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)
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index 9a1a27c8ed..aedcd81e52 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -64,7 +64,7 @@ module ActionMailer
raise "Delivery method cannot be nil"
when Symbol
if klass = delivery_methods[method]
- mail.delivery_method(klass,(send(:"#{method}_settings") || {}).merge!(options || {}))
+ mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {}))
else
raise "Invalid delivery method #{method.inspect}"
end
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
new file mode 100644
index 0000000000..b564813ccf
--- /dev/null
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -0,0 +1,15 @@
+module ActionMailer
+ # Returns the version of the currently loaded ActionMailer as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index c108156792..eb6fb11d81 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -1,3 +1,5 @@
+require 'active_support/log_subscriber'
+
module ActionMailer
# Implements the ActiveSupport::LogSubscriber for logging notifications when
# email is delivered and received.
@@ -17,6 +19,13 @@ module ActionMailer
debug(event.payload[:mail])
end
+ # An email was generated.
+ def process(event)
+ mailer = event.payload[:mailer]
+ action = event.payload[:action]
+ debug("\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms")
+ end
+
# Use the logger configured for ActionMailer::Base
def logger
ActionMailer::Base.logger
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index 8d6e082d02..54ad9f066f 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -50,7 +50,7 @@ module ActionMailer
end
indentation = " " * indent
- sentences.map { |sentence|
+ sentences.map! { |sentence|
"#{indentation}#{sentence.join(' ')}"
}.join "\n"
end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
new file mode 100644
index 0000000000..33a9faa7e7
--- /dev/null
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -0,0 +1,104 @@
+require 'active_support/descendants_tracker'
+
+module ActionMailer
+ module Previews #:nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ # Set the location of mailer previews through app configuration:
+ #
+ # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
+ #
+ mattr_accessor :preview_path, instance_writer: false
+
+ # :nodoc:
+ mattr_accessor :preview_interceptors, instance_writer: false
+ self.preview_interceptors = []
+
+ # Register one or more Interceptors which will be called before mail is previewed.
+ def register_preview_interceptors(*interceptors)
+ interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
+ end
+
+ # Register an Interceptor which will be called before mail is previewed.
+ # 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.
+ def register_preview_interceptor(interceptor)
+ preview_interceptor = case interceptor
+ when String, Symbol
+ interceptor.to_s.camelize.constantize
+ else
+ interceptor
+ end
+
+ unless preview_interceptors.include?(preview_interceptor)
+ preview_interceptors << preview_interceptor
+ end
+ end
+ end
+ end
+
+ class Preview
+ extend ActiveSupport::DescendantsTracker
+
+ class << self
+ # Returns all mailer preview classes
+ def all
+ load_previews if descendants.empty?
+ descendants
+ end
+
+ # Returns the mail object for the given email name. The registered preview
+ # interceptors will be informed so that they can transform the message
+ # as they would if the mail was actually being delivered.
+ def call(email)
+ preview = self.new
+ message = preview.public_send(email)
+ inform_preview_interceptors(message)
+ message
+ end
+
+ # Returns all of the available email previews
+ def emails
+ public_instance_methods(false).map(&:to_s).sort
+ end
+
+ # Returns true if the email exists
+ def email_exists?(email)
+ emails.include?(email)
+ end
+
+ # Returns true if the preview exists
+ def exists?(preview)
+ all.any?{ |p| p.preview_name == preview }
+ end
+
+ # Find a mailer preview by its underscored class name
+ def find(preview)
+ all.find{ |p| p.preview_name == preview }
+ end
+
+ # Returns the underscored name of the mailer preview without the suffix
+ def preview_name
+ name.sub(/Preview$/, '').underscore
+ end
+
+ protected
+ def load_previews #:nodoc:
+ if preview_path
+ Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file }
+ end
+ end
+
+ def preview_path #:nodoc:
+ Base.preview_path
+ end
+
+ def inform_preview_interceptors(message) #:nodoc:
+ Base.preview_interceptors.each do |interceptor|
+ interceptor.previewing_email(message)
+ end
+ end
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 7677ff3a7c..8d1e40297b 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -19,6 +19,10 @@ module ActionMailer
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
+ if Rails.env.development?
+ options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
+ end
+
# make sure readers methods get compiled
options.asset_host ||= app.config.asset_host
options.relative_url_root ||= app.config.relative_url_root
@@ -40,5 +44,11 @@ module ActionMailer
config.compile_methods! if config.respond_to?(:compile_methods!)
end
end
+
+ config.after_initialize do
+ if ActionMailer::Base.preview_path
+ ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path
+ end
+ end
end
end
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 9d00091972..a98aec913f 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActionMailer
- # Returns the version of the currently loaded ActionMailer as a Gem::Version
+ # Returns the version of the currently loaded ActionMailer as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActionMailer.version.segments
- STRING = ActionMailer.version.to_s
+ gem_version
end
end