aboutsummaryrefslogtreecommitdiffstats
path: root/actionmailer/lib/action_mailer/message_delivery.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionmailer/lib/action_mailer/message_delivery.rb')
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb90
1 files changed, 68 insertions, 22 deletions
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index 5fcb5a0c88..a57980c322 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -1,9 +1,10 @@
-require 'delegate'
+# frozen_string_literal: true
-module ActionMailer
+require "delegate"
+module ActionMailer
# The <tt>ActionMailer::MessageDelivery</tt> class is used by
- # <tt>ActionMailer::Base</tt> when creating a new mailer.
+ # ActionMailer::Base when creating a new mailer.
# <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
# created <tt>Mail::Message</tt>. You can get direct access to the
# <tt>Mail::Message</tt>, deliver the email or schedule the email to be sent
@@ -14,29 +15,35 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
# Notifier.welcome(User.first).message # a Mail::Message object
class MessageDelivery < Delegator
- def initialize(mailer, mail_method, *args) #:nodoc:
- @mailer = mailer
- @mail_method = mail_method
- @args = args
+ def initialize(mailer_class, action, *args) #:nodoc:
+ @mailer_class, @action, @args = mailer_class, action, args
+
+ # The mail is only processed if we try to call any methods on it.
+ # Typical usage will leave it unloaded and call deliver_later.
+ @processed_mailer = nil
+ @mail_message = nil
end
+ # Method calls are delegated to the Mail::Message that's ready to deliver.
def __getobj__ #:nodoc:
- @obj ||= begin
- mailer = @mailer.new
- mailer.process @mail_method, *@args
- mailer.message
- end
+ @mail_message ||= processed_mailer.message
end
- def __setobj__(obj) #:nodoc:
- @obj = obj
+ # Unused except for delegator internals (dup, marshalling).
+ def __setobj__(mail_message) #:nodoc:
+ @mail_message = mail_message
end
- # Returns the Mail::Message object
+ # Returns the resulting Mail::Message
def message
__getobj__
end
+ # Was the delegate loaded, causing the mailer action to be processed?
+ def processed?
+ @processed_mailer || @mail_message
+ end
+
# Enqueues the email to be delivered through Active Job. When the
# job runs it will send the email using +deliver_now!+. That means
# that the message will be sent bypassing checking +perform_deliveries+
@@ -51,7 +58,15 @@ module ActionMailer
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
# * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
# * <tt>:queue</tt> - Enqueue the email on the specified queue
- def deliver_later!(options={})
+ #
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
+ # +delivery_job+.
+ #
+ # class AccountRegistrationMailer < ApplicationMailer
+ # self.delivery_job = RegistrationDeliveryJob
+ # end
+ def deliver_later!(options = {})
enqueue_delivery :deliver_now!, options
end
@@ -67,7 +82,15 @@ module ActionMailer
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
# * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
# * <tt>:queue</tt> - Enqueue the email on the specified queue.
- def deliver_later(options={})
+ #
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
+ # +delivery_job+.
+ #
+ # class AccountRegistrationMailer < ApplicationMailer
+ # self.delivery_job = RegistrationDeliveryJob
+ # end
+ def deliver_later(options = {})
enqueue_delivery :deliver_now, options
end
@@ -77,7 +100,9 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now!
#
def deliver_now!
- message.deliver!
+ processed_mailer.handle_exceptions do
+ message.deliver!
+ end
end
# Delivers an email:
@@ -85,14 +110,35 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_now
#
def deliver_now
- message.deliver
+ processed_mailer.handle_exceptions do
+ message.deliver
+ end
end
private
+ # Returns the processed Mailer instance. We keep this instance
+ # on hand so we can delegate exception handling to it.
+ def processed_mailer
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
+ mailer.process @action, *@args
+ end
+ end
- def enqueue_delivery(delivery_method, options={})
- args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
- ActionMailer::DeliveryJob.set(options).perform_later(*args)
+ def enqueue_delivery(delivery_method, options = {})
+ if processed?
+ ::Kernel.raise "You've accessed the message before asking to " \
+ "deliver it later, so you may have made local changes that would " \
+ "be silently lost if we enqueued a job to deliver it. Why? Only " \
+ "the mailer method *arguments* are passed with the delivery job! " \
+ "Do not access the message in any way if you mean to deliver it " \
+ "later. Workarounds: 1. don't touch the message before calling " \
+ "#deliver_later, 2. only touch the message *within your mailer " \
+ "method*, or 3. use a custom Active Job instead of #deliver_later."
+ else
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args
+ job = @mailer_class.delivery_job
+ job.set(options).perform_later(*args)
+ end
end
end
end