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
|
* Added `deliver_enqueued_emails` to `ActionMailer::TestHelper`. This method
|
||||||
delivers all enqueued email jobs.
|
delivers all enqueued email jobs.
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ module ActionMailer
|
|||||||
end
|
end
|
||||||
|
|
||||||
autoload :Base
|
autoload :Base
|
||||||
|
autoload :Callbacks
|
||||||
autoload :DeliveryMethods
|
autoload :DeliveryMethods
|
||||||
autoload :InlinePreviewInterceptor
|
autoload :InlinePreviewInterceptor
|
||||||
autoload :MailHelper
|
autoload :MailHelper
|
||||||
|
@ -316,12 +316,14 @@ module ActionMailer
|
|||||||
#
|
#
|
||||||
# = Callbacks
|
# = Callbacks
|
||||||
#
|
#
|
||||||
# You can specify callbacks using <tt>before_action</tt> and <tt>after_action</tt> for configuring your messages.
|
# 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
|
# and using <tt>before_deliver</tt> and <tt>after_deliver</tt> for wrapping the delivery process.
|
||||||
# messages sent out by a certain mailer class:
|
# 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
|
# class NotifierMailer < ApplicationMailer
|
||||||
# before_action :add_inline_attachment!
|
# before_action :add_inline_attachment!
|
||||||
|
# after_deliver :log_delivery
|
||||||
#
|
#
|
||||||
# def welcome
|
# def welcome
|
||||||
# mail
|
# mail
|
||||||
@ -331,9 +333,13 @@ module ActionMailer
|
|||||||
# def add_inline_attachment!
|
# def add_inline_attachment!
|
||||||
# attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
|
# attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
|
||||||
# end
|
# end
|
||||||
|
#
|
||||||
|
# def log_delivery
|
||||||
|
# Rails.logger.info "Sent email with message id '#{message.message_id}' at #{Time.current}."
|
||||||
|
# end
|
||||||
# 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
|
# AbstractController::Callbacks, so you can define and configure
|
||||||
# callbacks in the same manner that you would use callbacks in classes that
|
# callbacks in the same manner that you would use callbacks in classes that
|
||||||
# inherit from ActionController::Base.
|
# 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>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.
|
# <tt>delivery_job</tt>. Mailers can set this to use a custom queue name.
|
||||||
class Base < AbstractController::Base
|
class Base < AbstractController::Base
|
||||||
|
include Callbacks
|
||||||
include DeliveryMethods
|
include DeliveryMethods
|
||||||
include QueuedDelivery
|
include QueuedDelivery
|
||||||
include Rescuable
|
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!
|
def deliver_now!
|
||||||
processed_mailer.handle_exceptions do
|
processed_mailer.handle_exceptions do
|
||||||
|
processed_mailer.run_callbacks(:deliver) do
|
||||||
message.deliver!
|
message.deliver!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Delivers an email:
|
# Delivers an email:
|
||||||
#
|
#
|
||||||
@ -118,13 +120,15 @@ def deliver_now!
|
|||||||
#
|
#
|
||||||
def deliver_now
|
def deliver_now
|
||||||
processed_mailer.handle_exceptions do
|
processed_mailer.handle_exceptions do
|
||||||
|
processed_mailer.run_callbacks(:deliver) do
|
||||||
message.deliver
|
message.deliver
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Returns the processed Mailer instance. We keep this instance
|
# 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
|
def processed_mailer
|
||||||
@processed_mailer ||= @mailer_class.new.tap do |mailer|
|
@processed_mailer ||= @mailer_class.new.tap do |mailer|
|
||||||
mailer.process @action, *@args
|
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
|
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.
|
class similar to controllers.
|
||||||
|
|
||||||
* You could use a `before_action` to set instance variables, populate the mail
|
* You could use a `before_action` to set instance variables, populate the mail
|
||||||
@ -776,11 +777,16 @@ class UserMailer < ApplicationMailer
|
|||||||
end
|
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_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_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_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
|
Using Action Mailer Helpers
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -1290,6 +1290,7 @@ class Uploader {
|
|||||||
|
|
||||||
To implement customized authentication, a new controller must be created on
|
To implement customized authentication, a new controller must be created on
|
||||||
the Rails application, similar to the following:
|
the Rails application, similar to the following:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class DirectUploadsController < ActiveStorage::DirectUploadsController
|
class DirectUploadsController < ActiveStorage::DirectUploadsController
|
||||||
skip_before_action :verify_authenticity_token
|
skip_before_action :verify_authenticity_token
|
||||||
|
Loading…
Reference in New Issue
Block a user