aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailer
diff options
context:
space:
mode:
Diffstat (limited to 'actionmailer')
-rw-r--r--actionmailer/CHANGELOG.md15
-rw-r--r--actionmailer/lib/action_mailer.rb1
-rw-r--r--actionmailer/lib/action_mailer/base.rb23
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb2
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb6
-rw-r--r--actionmailer/lib/action_mailer/parameterized.rb152
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb4
-rw-r--r--actionmailer/test/base_test.rb19
-rw-r--r--actionmailer/test/mailers/params_mailer.rb11
-rw-r--r--actionmailer/test/message_delivery_test.rb4
-rw-r--r--actionmailer/test/parameterized_test.rb55
-rw-r--r--actionmailer/test/test_helper_test.rb18
12 files changed, 290 insertions, 20 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index de8abcccfe..ee33450b45 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,18 @@
+## Rails 5.1.0.beta1 (February 23, 2017) ##
+
+* Add `:args` to `process.action_mailer` event.
+
+ *Yuji Yaginuma*
+
+* Add parameterized invocation of mailers as a way to share before filters and defaults between actions.
+ See `ActionMailer::Parameterized` for a full example of the benefit.
+
+ *DHH*
+
+* Allow lambdas to be used as lazy defaults in addition to procs.
+
+ *DHH*
+
* Mime type: allow to custom content type when setting body in headers
and attachments.
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index cf2c0f37e3..211190560a 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -42,6 +42,7 @@ module ActionMailer
autoload :DeliveryMethods
autoload :InlinePreviewInterceptor
autoload :MailHelper
+ autoload :Parameterized
autoload :Preview
autoload :Previews, "action_mailer/preview"
autoload :TestCase
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 5aa3c4fa97..6849f5c0f9 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -133,6 +133,9 @@ module ActionMailer
#
# config.action_mailer.default_url_options = { host: "example.com" }
#
+ # You can also define a <tt>default_url_options</tt> method on individual mailers to override these
+ # default settings per-mailer.
+ #
# By default when <tt>config.force_ssl</tt> is true, URLs generated for hosts will use the HTTPS protocol.
#
# = Sending mail
@@ -288,20 +291,19 @@ module ActionMailer
# content_description: 'This is a description'
# end
#
- # 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:
+ # Finally, Action Mailer also supports passing <tt>Proc</tt> and <tt>Lambda</tt> objects into the default hash,
+ # so you can define methods that evaluate as the message is being generated:
#
# class NotifierMailer < ApplicationMailer
- # default 'X-Special-Header' => Proc.new { my_method }
+ # default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address }
#
# private
- #
# def my_method
# 'some complex call'
# end
# end
#
- # Note that the proc is evaluated right at the start of the mail message generation, so if you
+ # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you
# set something in the default hash using a proc, and then set the same thing inside of your
# mailer method, it will get overwritten by the mailer method.
#
@@ -324,7 +326,6 @@ module ActionMailer
# end
#
# private
- #
# def add_inline_attachment!
# attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
# end
@@ -430,10 +431,11 @@ module ActionMailer
# * <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>.
+ # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. Defaults to +mailers+.
class Base < AbstractController::Base
include DeliveryMethods
include Rescuable
+ include Parameterized
include Previews
abstract!
@@ -599,7 +601,8 @@ module ActionMailer
def process(method_name, *args) #:nodoc:
payload = {
mailer: self.class.name,
- action: method_name
+ action: method_name,
+ args: args
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
@@ -888,7 +891,7 @@ module ActionMailer
default_values = self.class.default.map do |key, value|
[
key,
- value.is_a?(Proc) ? instance_eval(&value) : value
+ value.is_a?(Proc) ? instance_exec(&value) : value
]
end.to_h
@@ -972,7 +975,7 @@ module ActionMailer
end
def instrument_name
- "action_mailer"
+ "action_mailer".freeze
end
ActiveSupport.run_load_hooks(:action_mailer, self)
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index 7dafceef2b..de2d71bd3e 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -8,7 +8,7 @@ module ActionMailer
MAJOR = 5
MINOR = 1
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
index 9087d335fa..980415afe0 100644
--- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -26,7 +26,7 @@ module ActionMailer
def transform! #:nodoc:
return message if html_part.blank?
- html_source.gsub!(PATTERN) do |match|
+ html_part.body = html_part.decoded.gsub(PATTERN) do |match|
if part = find_part(match[9..-2])
%[src="#{data_url(part)}"]
else
@@ -46,10 +46,6 @@ module ActionMailer
@html_part ||= message.html_part
end
- def html_source
- html_part.body.raw_source
- end
-
def data_url(part)
"data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
end
diff --git a/actionmailer/lib/action_mailer/parameterized.rb b/actionmailer/lib/action_mailer/parameterized.rb
new file mode 100644
index 0000000000..3acacc1f14
--- /dev/null
+++ b/actionmailer/lib/action_mailer/parameterized.rb
@@ -0,0 +1,152 @@
+module ActionMailer
+ # Provides the option to parameterize mailers in order to share instance variable
+ # setup, processing, and common headers.
+ #
+ # Consider this example that does not use parameterization:
+ #
+ # class InvitationsMailer < ApplicationMailer
+ # def account_invitation(inviter, invitee)
+ # @account = inviter.account
+ # @inviter = inviter
+ # @invitee = invitee
+ #
+ # subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ #
+ # def project_invitation(project, inviter, invitee)
+ # @account = inviter.account
+ # @project = project
+ # @inviter = inviter
+ # @invitee = invitee
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+ #
+ # subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ #
+ # def bulk_project_invitation(projects, inviter, invitee)
+ # @account = inviter.account
+ # @projects = projects.sort_by(&:name)
+ # @inviter = inviter
+ # @invitee = invitee
+ #
+ # subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ # end
+ #
+ # InvitationsMailer.account_invitation(person_a, person_b).deliver_later
+ #
+ # Using parameterized mailers, this can be rewritten as:
+ #
+ # class InvitationsMailer < ApplicationMailer
+ # before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+ # before_action { @account = params[:inviter].account }
+ #
+ # default to: -> { @invitee.email_address },
+ # from: -> { common_address(@inviter) },
+ # reply_to: -> { @inviter.email_address_with_name }
+ #
+ # def account_invitation
+ # mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ # end
+ #
+ # def project_invitation
+ # @project = params[:project]
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+ #
+ # mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ # end
+ #
+ # def bulk_project_invitation
+ # @projects = params[:projects].sort_by(&:name)
+ #
+ # mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
+ # end
+ # end
+ #
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+ module Parameterized
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :params
+ end
+
+ module ClassMethods
+ # Provide the parameters to the mailer in order to use them in the instance methods and callbacks.
+ #
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+ #
+ # See Parameterized documentation for full example.
+ def with(params)
+ ActionMailer::Parameterized::Mailer.new(self, params)
+ end
+ end
+
+ class Mailer # :nodoc:
+ def initialize(mailer, params)
+ @mailer, @params = mailer, params
+ end
+
+ private
+ def method_missing(method_name, *args)
+ if @mailer.action_methods.include?(method_name.to_s)
+ ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args)
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(method, include_all = false)
+ @mailer.respond_to?(method, include_all)
+ end
+ end
+
+ class MessageDelivery < ActionMailer::MessageDelivery # :nodoc:
+ def initialize(mailer_class, action, params, *args)
+ super(mailer_class, action, *args)
+ @params = params
+ end
+
+ private
+ def processed_mailer
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
+ mailer.params = @params
+ mailer.process @action, *@args
+ end
+ end
+
+ def enqueue_delivery(delivery_method, options = {})
+ if processed?
+ super
+ else
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args
+ ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args)
+ end
+ end
+ end
+
+ class DeliveryJob < ActionMailer::DeliveryJob # :nodoc:
+ def perform(mailer, mail_method, delivery_method, params, *args)
+ mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
+ end
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index c17ecad4c6..c30fb1fc18 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -88,7 +88,7 @@ module ActionMailer
# end
# end
def assert_enqueued_emails(number, &block)
- assert_enqueued_jobs number, only: ActionMailer::DeliveryJob, &block
+ assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
end
# Asserts that no emails are enqueued for later delivery.
@@ -107,7 +107,7 @@ module ActionMailer
# end
# end
def assert_no_enqueued_emails(&block)
- assert_no_enqueued_jobs only: ActionMailer::DeliveryJob, &block
+ assert_no_enqueued_jobs only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
end
end
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 490aaf33fc..61960d411d 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -834,6 +834,25 @@ class BaseTest < ActiveSupport::TestCase
assert_equal "special indeed!", mail["X-Special-Header"].to_s
end
+ test "notification for process" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe("process.action_mailer") do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ BaseMailer.welcome(body: "Hello there").deliver_now
+
+ assert_equal 1, events.length
+ assert_equal "process.action_mailer", events[0].name
+ assert_equal "BaseMailer", events[0].payload[:mailer]
+ assert_equal :welcome, events[0].payload[:action]
+ assert_equal [{ body: "Hello there" }], events[0].payload[:args]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "process.action_mailer"
+ end
+ end
+
private
# Execute the block setting the given values and restoring old values after
diff --git a/actionmailer/test/mailers/params_mailer.rb b/actionmailer/test/mailers/params_mailer.rb
new file mode 100644
index 0000000000..4c0fae6d91
--- /dev/null
+++ b/actionmailer/test/mailers/params_mailer.rb
@@ -0,0 +1,11 @@
+class ParamsMailer < ActionMailer::Base
+ before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+
+ default to: Proc.new { @invitee }, from: -> { @inviter }
+
+ def invitation
+ mail(subject: "Welcome to the project!") do |format|
+ format.text { render plain: "So says #{@inviter}" }
+ end
+ end
+end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index a79d77e1e5..e3d15816d4 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -76,14 +76,14 @@ class MessageDeliveryTest < ActiveSupport::TestCase
test "should enqueue a delivery with a delay" do
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f + 600.seconds, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current + 600.seconds, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
@mail.deliver_later wait: 600.seconds
end
end
end
test "should enqueue a delivery at a specific time" do
- later_time = Time.now.to_f + 3600
+ later_time = Time.current + 1.hour
assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
@mail.deliver_later wait_until: later_time
end
diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb
new file mode 100644
index 0000000000..914ed12312
--- /dev/null
+++ b/actionmailer/test/parameterized_test.rb
@@ -0,0 +1,55 @@
+require "abstract_unit"
+require "active_job"
+require "mailers/params_mailer"
+
+class ParameterizedTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+
+ setup do
+ @previous_logger = ActiveJob::Base.logger
+ ActiveJob::Base.logger = Logger.new(nil)
+
+ @previous_delivery_method = ActionMailer::Base.delivery_method
+ ActionMailer::Base.delivery_method = :test
+
+ @previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name
+ ActionMailer::Base.deliver_later_queue_name = :test_queue
+ ActionMailer::Base.delivery_method = :test
+
+ @mail = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com").invitation
+ end
+
+ teardown do
+ ActiveJob::Base.logger = @previous_logger
+ ParamsMailer.deliveries.clear
+
+ ActionMailer::Base.delivery_method = @previous_delivery_method
+ ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
+ end
+
+ test "parameterized headers" do
+ assert_equal(["jason@basecamp.com"], @mail.to)
+ assert_equal(["david@basecamp.com"], @mail.from)
+ assert_equal("So says david@basecamp.com", @mail.body.encoded)
+ end
+
+ test "enqueue the email with params" do
+ assert_performed_with(job: ActionMailer::Parameterized::DeliveryJob, args: ["ParamsMailer", "invitation", "deliver_now", { inviter: "david@basecamp.com", invitee: "jason@basecamp.com" } ]) do
+ @mail.deliver_later
+ end
+ end
+
+ test "respond_to?" do
+ mailer = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com")
+
+ assert_respond_to mailer, :invitation
+ assert_not_respond_to mailer, :anything
+
+ invitation = mailer.method(:invitation)
+ assert_equal Method, invitation.class
+
+ assert_raises(NameError) do
+ invitation = mailer.method(:anything)
+ end
+ end
+end
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 31ac5a5211..876e9b0634 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -143,6 +143,16 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_enqueued_parameterized_emails
+ assert_nothing_raised do
+ assert_enqueued_emails 1 do
+ silence_stream($stdout) do
+ TestHelperMailer.with(a: 1).test.deliver_later
+ end
+ end
+ end
+ end
+
def test_assert_enqueued_emails_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_emails 2 do
@@ -176,6 +186,14 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_no_enqueued_parameterized_emails
+ assert_nothing_raised do
+ assert_no_enqueued_emails do
+ TestHelperMailer.with(a: 1).test.deliver_now
+ end
+ end
+ end
+
def test_assert_no_enqueued_emails_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_enqueued_emails do