ActionMailer::Base can unregister observer(s) and interceptor(s). (#32207)

* ActionMailer::Base can unregister observer(s) and interceptor(s).

One or multiple mail observers can be unregistered using
`ActionMailer::Base.unregister_observers` or
`ActionMailer::Base.unregister_observer`.

One or multiple mail interceptors can be unregistered using
`ActionMailer::Base.unregister_interceptors` or
`ActionMailer::Base.unregister_interceptor`.

For preview interceptors, it's possible to use
`ActionMailer::Base.unregister_preview_interceptors` or
`ActionMailer::Base.unregister_preview_interceptor`.

* Ensure to be reset registered observer(s) and interceptor(s)

* Add explanation to CHANGELOG

* Add original author's name

[Kota Miyake + Rafael Mendonça França + Claudio Ortolina]
This commit is contained in:
Kota Miyake 2018-05-31 05:36:24 +08:00 committed by Rafael França
parent e6ef1fe056
commit b74edd37c5
4 changed files with 135 additions and 19 deletions

View File

@ -6,6 +6,14 @@
*Gannon McGibbon*
* Add `Base.unregister_observer`, `Base.unregister_observers`,
`Base.unregister_interceptor`, `Base.unregister_interceptors`,
`Base.unregister_preview_interceptor` and `Base.unregister_preview_interceptors`.
This makes it possible to dynamically add and remove email observers and
interceptors at runtime in the same way they're registered.
*Claudio Ortolina*, *Kota Miyake*
* Rails 6 requires Ruby 2.4.1 or newer.
*Jeremy Daer*

View File

@ -475,11 +475,21 @@ module ActionMailer
observers.flatten.compact.each { |observer| register_observer(observer) }
end
# Unregister one or more previously registered Observers.
def unregister_observers(*observers)
observers.flatten.compact.each { |observer| unregister_observer(observer) }
end
# Register one or more Interceptors which will be called before mail is sent.
def register_interceptors(*interceptors)
interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
end
# Unregister one or more previously registered Interceptors.
def unregister_interceptors(*interceptors)
interceptors.flatten.compact.each { |interceptor| unregister_interceptor(interceptor) }
end
# Register an Observer which will be notified when mail is delivered.
# 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.
@ -487,6 +497,13 @@ module ActionMailer
Mail.register_observer(observer_class_for(observer))
end
# Unregister a previously registered Observer.
# 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 unregister_observer(observer)
Mail.unregister_observer(observer_class_for(observer))
end
# Register an Interceptor which will be called before mail is sent.
# 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.
@ -494,6 +511,13 @@ module ActionMailer
Mail.register_interceptor(observer_class_for(interceptor))
end
# Unregister a previously registered Interceptor.
# 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 unregister_interceptor(interceptor)
Mail.unregister_interceptor(observer_class_for(interceptor))
end
def observer_class_for(value) # :nodoc:
case value
when String, Symbol

View File

@ -31,22 +31,39 @@ module ActionMailer
interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
end
# Unregister one or more previously registered Interceptors.
def unregister_preview_interceptors(*interceptors)
interceptors.flatten.compact.each { |interceptor| unregister_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 constantized.
def register_preview_interceptor(interceptor)
preview_interceptor = \
preview_interceptor = interceptor_class_for(interceptor)
unless preview_interceptors.include?(preview_interceptor)
preview_interceptors << preview_interceptor
end
end
# Unregister a previously registered Interceptor.
# Either a class or a string can be passed in as the Interceptor. If a
# string is passed in it will be constantized.
def unregister_preview_interceptor(interceptor)
preview_interceptors.delete(interceptor_class_for(interceptor))
end
private
def interceptor_class_for(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

View File

@ -618,37 +618,52 @@ class BaseTest < ActiveSupport::TestCase
end
end
test "you can register an observer to the mail object that gets informed on email delivery" do
test "you can register and unregister an observer to the mail object that gets informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
ActionMailer::Base.unregister_observer(MyObserver)
assert_not_called(MyObserver, :delivered_email, returns: mail) do
mail.deliver_now
end
end
end
test "you can register an observer using its stringified name to the mail object that gets informed on email delivery" do
test "you can register and unregister an observer using its stringified name to the mail object that gets informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observer("BaseTest::MyObserver")
mail = BaseMailer.welcome
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
ActionMailer::Base.unregister_observer("BaseTest::MyObserver")
assert_not_called(MyObserver, :delivered_email, returns: mail) do
mail.deliver_now
end
end
end
test "you can register an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do
test "you can register and unregister an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observer(:"base_test/my_observer")
mail = BaseMailer.welcome
assert_called_with(MyObserver, :delivered_email, [mail]) do
mail.deliver_now
end
ActionMailer::Base.unregister_observer(:"base_test/my_observer")
assert_not_called(MyObserver, :delivered_email, returns: mail) do
mail.deliver_now
end
end
end
test "you can register multiple observers to the mail object that both get informed on email delivery" do
test "you can register and unregister multiple observers to the mail object that both get informed on email delivery" do
mail_side_effects do
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
mail = BaseMailer.welcome
@ -657,6 +672,14 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver_now
end
end
ActionMailer::Base.unregister_observers("BaseTest::MyObserver", MySecondObserver)
assert_not_called(MyObserver, :delivered_email, returns: mail) do
mail.deliver_now
end
assert_not_called(MySecondObserver, :delivered_email, returns: mail) do
mail.deliver_now
end
end
end
@ -670,37 +693,52 @@ class BaseTest < ActiveSupport::TestCase
def self.previewing_email(mail); end
end
test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do
test "you can register and unregister an interceptor to the mail object that gets passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
ActionMailer::Base.unregister_interceptor(MyInterceptor)
assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
mail.deliver_now
end
end
end
test "you can register an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
test "you can register and unregister an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
mail = BaseMailer.welcome
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
ActionMailer::Base.unregister_interceptor("BaseTest::MyInterceptor")
assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
mail.deliver_now
end
end
end
test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do
test "you can register and unregister an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
mail = BaseMailer.welcome
assert_called_with(MyInterceptor, :delivering_email, [mail]) do
mail.deliver_now
end
ActionMailer::Base.unregister_interceptor(:"base_test/my_interceptor")
assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
mail.deliver_now
end
end
end
test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
test "you can register and unregister multiple interceptors to the mail object that both get passed the mail object before delivery" do
mail_side_effects do
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
@ -709,6 +747,14 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver_now
end
end
ActionMailer::Base.unregister_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
assert_not_called(MyInterceptor, :delivering_email, returns: mail) do
mail.deliver_now
end
assert_not_called(MySecondInterceptor, :delivering_email, returns: mail) do
mail.deliver_now
end
end
end
@ -888,8 +934,6 @@ class BaseTest < ActiveSupport::TestCase
klass.default_params = old
end
# A simple hack to restore the observers and interceptors for Mail, as it
# does not have an unregister API yet.
def mail_side_effects
old_observers = Mail.class_variable_get(:@@delivery_notification_observers)
old_delivery_interceptors = Mail.class_variable_get(:@@delivery_interceptors)
@ -928,7 +972,7 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
def self.previewing_email(mail); end
end
test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
test "you can register and unregister a preview interceptor to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(MyInterceptor)
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@ -938,9 +982,14 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
ActionMailer::Base.unregister_preview_interceptor(MyInterceptor)
assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
BaseMailerPreview.call(:welcome)
end
end
test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
test "you can register and unregister a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@ -950,9 +999,14 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
ActionMailer::Base.unregister_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
BaseMailerPreview.call(:welcome)
end
end
test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
test "you can register and unregister a preview interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@ -962,9 +1016,14 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
ActionMailer::Base.unregister_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
BaseMailerPreview.call(:welcome)
end
end
test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
test "you can register and unregister multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
stub_any_instance(BaseMailerPreview) do |instance|
@ -976,6 +1035,14 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
ActionMailer::Base.unregister_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
assert_not_called(MyInterceptor, :previewing_email, returns: mail) do
BaseMailerPreview.call(:welcome)
end
assert_not_called(MySecondInterceptor, :previewing_email, returns: mail) do
BaseMailerPreview.call(:welcome)
end
end
end