Add *_deliver
callbacks for Action Mailer
This commit is contained in:
parent
4d171a4bde
commit
468d806406
@ -1,3 +1,17 @@
|
||||
* Added `*_deliver` callbacks to `ActionMailer::Base` that wrap mail message delivery.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
class EventsMailer < ApplicationMailer
|
||||
after_deliver do
|
||||
User.find_by(email: message.to.first).update(email_provider_id: message.message_id, emailed_at: Time.current)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
*Ben Sheldon*
|
||||
|
||||
* Added `deliver_enqueued_emails` to `ActionMailer::TestHelper`. This method
|
||||
delivers all enqueued email jobs.
|
||||
|
||||
|
@ -43,6 +43,7 @@ module ActionMailer
|
||||
end
|
||||
|
||||
autoload :Base
|
||||
autoload :Callbacks
|
||||
autoload :DeliveryMethods
|
||||
autoload :InlinePreviewInterceptor
|
||||
autoload :MailHelper
|
||||
|
@ -316,12 +316,14 @@ module ActionMailer
|
||||
#
|
||||
# = Callbacks
|
||||
#
|
||||
# You can specify callbacks using <tt>before_action</tt> and <tt>after_action</tt> for configuring your messages.
|
||||
# This may be useful, for example, when you want to add default inline attachments for all
|
||||
# messages sent out by a certain mailer class:
|
||||
# You can specify callbacks using <tt>before_action</tt> and <tt>after_action</tt> for configuring your messages,
|
||||
# and using <tt>before_deliver</tt> and <tt>after_deliver</tt> for wrapping the delivery process.
|
||||
# For example, when you want to add default inline attachments and log delivery for all messages
|
||||
# sent out by a certain mailer class:
|
||||
#
|
||||
# class NotifierMailer < ApplicationMailer
|
||||
# before_action :add_inline_attachment!
|
||||
# after_deliver :log_delivery
|
||||
#
|
||||
# def welcome
|
||||
# mail
|
||||
@ -331,9 +333,13 @@ module ActionMailer
|
||||
# def add_inline_attachment!
|
||||
# attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
|
||||
# end
|
||||
#
|
||||
# def log_delivery
|
||||
# Rails.logger.info "Sent email with message id '#{message.message_id}' at #{Time.current}."
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Callbacks in Action Mailer are implemented using
|
||||
# Action callbacks in Action Mailer 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.
|
||||
@ -466,6 +472,7 @@ module ActionMailer
|
||||
# * <tt>deliver_later_queue_name</tt> - The queue name used by <tt>deliver_later</tt> with the default
|
||||
# <tt>delivery_job</tt>. Mailers can set this to use a custom queue name.
|
||||
class Base < AbstractController::Base
|
||||
include Callbacks
|
||||
include DeliveryMethods
|
||||
include QueuedDelivery
|
||||
include Rescuable
|
||||
|
31
actionmailer/lib/action_mailer/callbacks.rb
Normal file
31
actionmailer/lib/action_mailer/callbacks.rb
Normal file
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionMailer
|
||||
module Callbacks
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :deliver, skip_after_callbacks_if_terminated: true
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Defines a callback that will get called right before the
|
||||
# message is sent to the delivery method.
|
||||
def before_deliver(*filters, &blk)
|
||||
set_callback(:deliver, :before, *filters, &blk)
|
||||
end
|
||||
|
||||
# Defines a callback that will get called right after the
|
||||
# message's delivery method is finished.
|
||||
def after_deliver(*filters, &blk)
|
||||
set_callback(:deliver, :after, *filters, &blk)
|
||||
end
|
||||
|
||||
# Defines a callback that will get called around the message's deliver method.
|
||||
def around_deliver(*filters, &blk)
|
||||
set_callback(:deliver, :around, *filters, &blk)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -108,9 +108,11 @@ def deliver_later(options = {})
|
||||
#
|
||||
def deliver_now!
|
||||
processed_mailer.handle_exceptions do
|
||||
processed_mailer.run_callbacks(:deliver) do
|
||||
message.deliver!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Delivers an email:
|
||||
#
|
||||
@ -118,13 +120,15 @@ def deliver_now!
|
||||
#
|
||||
def deliver_now
|
||||
processed_mailer.handle_exceptions do
|
||||
processed_mailer.run_callbacks(:deliver) do
|
||||
message.deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Returns the processed Mailer instance. We keep this instance
|
||||
# on hand so we can delegate exception handling to it.
|
||||
# on hand so we can run callbacks and delegate exception handling to it.
|
||||
def processed_mailer
|
||||
@processed_mailer ||= @mailer_class.new.tap do |mailer|
|
||||
mailer.process @action, *@args
|
||||
|
80
actionmailer/test/callbacks_test.rb
Normal file
80
actionmailer/test/callbacks_test.rb
Normal file
@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "abstract_unit"
|
||||
require "mailers/callback_mailer"
|
||||
|
||||
class ActionMailerCallbacksTest < ActiveSupport::TestCase
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
setup do
|
||||
@previous_delivery_method = ActionMailer::Base.delivery_method
|
||||
ActionMailer::Base.delivery_method = :test
|
||||
CallbackMailer.rescue_from_error = nil
|
||||
CallbackMailer.after_deliver_instance = nil
|
||||
CallbackMailer.around_deliver_instance = nil
|
||||
CallbackMailer.abort_before_deliver = nil
|
||||
CallbackMailer.around_handles_error = nil
|
||||
end
|
||||
|
||||
teardown do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
ActionMailer::Base.delivery_method = @previous_delivery_method
|
||||
CallbackMailer.rescue_from_error = nil
|
||||
CallbackMailer.after_deliver_instance = nil
|
||||
CallbackMailer.around_deliver_instance = nil
|
||||
CallbackMailer.abort_before_deliver = nil
|
||||
CallbackMailer.around_handles_error = nil
|
||||
end
|
||||
|
||||
test "deliver_now should call after_deliver callback and can access sent message" do
|
||||
mail_delivery = CallbackMailer.test_message
|
||||
mail_delivery.deliver_now
|
||||
|
||||
assert_kind_of CallbackMailer, CallbackMailer.after_deliver_instance
|
||||
assert_not_empty CallbackMailer.after_deliver_instance.message.message_id
|
||||
assert_equal mail_delivery.message_id, CallbackMailer.after_deliver_instance.message.message_id
|
||||
assert_equal "test-receiver@test.com", CallbackMailer.after_deliver_instance.message.to.first
|
||||
end
|
||||
|
||||
test "deliver_now! should call after_deliver callback" do
|
||||
CallbackMailer.test_message.deliver_now
|
||||
|
||||
assert_kind_of CallbackMailer, CallbackMailer.after_deliver_instance
|
||||
end
|
||||
|
||||
test "before_deliver can abort the delivery and not run after_deliver callbacks" do
|
||||
CallbackMailer.abort_before_deliver = true
|
||||
|
||||
mail_delivery = CallbackMailer.test_message
|
||||
mail_delivery.deliver_now
|
||||
|
||||
assert_nil mail_delivery.message_id
|
||||
assert_nil CallbackMailer.after_deliver_instance
|
||||
end
|
||||
|
||||
test "deliver_later should call after_deliver callback and can access sent message" do
|
||||
perform_enqueued_jobs { CallbackMailer.test_message.deliver_later }
|
||||
assert_kind_of CallbackMailer, CallbackMailer.after_deliver_instance
|
||||
assert_not_empty CallbackMailer.after_deliver_instance.message.message_id
|
||||
end
|
||||
|
||||
test "around_deliver is called after rescue_from on action processing exceptions" do
|
||||
CallbackMailer.around_handles_error = true
|
||||
|
||||
CallbackMailer.test_raise_action.deliver_now
|
||||
assert CallbackMailer.rescue_from_error
|
||||
end
|
||||
|
||||
test "around_deliver is called before rescue_from on deliver! exceptions" do
|
||||
CallbackMailer.around_handles_error = true
|
||||
|
||||
stub_any_instance(Mail::TestMailer, instance: Mail::TestMailer.new({})) do |instance|
|
||||
instance.stub(:deliver!, proc { raise "boom deliver exception" }) do
|
||||
CallbackMailer.test_message.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
assert_kind_of CallbackMailer, CallbackMailer.after_deliver_instance
|
||||
assert_nil CallbackMailer.rescue_from_error
|
||||
end
|
||||
end
|
37
actionmailer/test/mailers/callback_mailer.rb
Normal file
37
actionmailer/test/mailers/callback_mailer.rb
Normal file
@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
CallbackMailerError = Class.new(StandardError)
|
||||
class CallbackMailer < ActionMailer::Base
|
||||
cattr_accessor :rescue_from_error
|
||||
cattr_accessor :after_deliver_instance
|
||||
cattr_accessor :around_deliver_instance
|
||||
cattr_accessor :abort_before_deliver
|
||||
cattr_accessor :around_handles_error
|
||||
|
||||
rescue_from CallbackMailerError do |error|
|
||||
@@rescue_from_error = error
|
||||
end
|
||||
|
||||
before_deliver do
|
||||
throw :abort if @@abort_before_deliver
|
||||
end
|
||||
|
||||
after_deliver do
|
||||
@@after_deliver_instance = self
|
||||
end
|
||||
|
||||
around_deliver do |mailer, block|
|
||||
@@around_deliver_instance = self
|
||||
block.call
|
||||
rescue StandardError
|
||||
raise unless @@around_handles_error
|
||||
end
|
||||
|
||||
def test_message(*)
|
||||
mail(from: "test-sender@test.com", to: "test-receiver@test.com", subject: "Test Subject", body: "Test Body")
|
||||
end
|
||||
|
||||
def test_raise_action
|
||||
raise CallbackMailerError, "boom action processing"
|
||||
end
|
||||
end
|
@ -697,9 +697,10 @@ Action Mailer Callbacks
|
||||
-----------------------
|
||||
|
||||
Action Mailer allows for you to specify a [`before_action`][], [`after_action`][] and
|
||||
[`around_action`][].
|
||||
[`around_action`][] to configure the message, and [`before_deliver`][], [`after_deliver`][] and
|
||||
[`around_deliver`][] to control the delivery.
|
||||
|
||||
* Filters can be specified with a block or a symbol to a method in the mailer
|
||||
* Callbacks can be specified with a block or a symbol to a method in the mailer
|
||||
class similar to controllers.
|
||||
|
||||
* You could use a `before_action` to set instance variables, populate the mail
|
||||
@ -776,11 +777,16 @@ class UserMailer < ApplicationMailer
|
||||
end
|
||||
```
|
||||
|
||||
* Mailer Filters abort further processing if body is set to a non-nil value.
|
||||
* You could use an `after_delivery` to record the delivery of the message.
|
||||
|
||||
* Mailer callbacks abort further processing if body is set to a non-nil value. `before_deliver` can abort with `throw :abort`.
|
||||
|
||||
[`after_action`]: https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-after_action
|
||||
[`after_deliver`]: https://api.rubyonrails.org/classes/ActionMailer/Callbacks/ClassMethods.html#method-i-after_deliver
|
||||
[`around_action`]: https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-around_action
|
||||
[`around_deliver`]: https://api.rubyonrails.org/classes/ActionMailer/Callbacks/ClassMethods.html#method-i-around_deliver
|
||||
[`before_action`]: https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-before_action
|
||||
[`before_deliver`]: https://api.rubyonrails.org/classes/ActionMailer/Callbacks/ClassMethods.html#method-i-before_deliver
|
||||
|
||||
Using Action Mailer Helpers
|
||||
---------------------------
|
||||
|
@ -1290,6 +1290,7 @@ class Uploader {
|
||||
|
||||
To implement customized authentication, a new controller must be created on
|
||||
the Rails application, similar to the following:
|
||||
|
||||
```ruby
|
||||
class DirectUploadsController < ActiveStorage::DirectUploadsController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
Loading…
Reference in New Issue
Block a user