Permalink
Browse files

added ability to halt callback chain

  • Loading branch information...
1 parent c68d45a commit 9b11a98b1546f6bd57d51a8bc1c417e5fb3af2ca @AnthonyCaliendo committed Jan 21, 2009
Showing with 85 additions and 9 deletions.
  1. +19 −4 README
  2. +16 −4 action_mailer_callbacks.rb
  3. +50 −1 action_mailer_callbacks_test.rb
View
23 README
@@ -46,18 +46,33 @@ class FooMailer < ActionMailer::Base
...
end
-== Limitations
+== What About Halting the Chain?
-CURRENTLY, THE before_deliver CALLBACK DOES NOT HALT EXECUTION. That may be added in the future, but in ther interim you will need to raise to halt.
+You can halt the chain in either a before or after callback. In order to do this, just call +halt_callback_chain+ in the block (or +self.class.halt_callback_chain+ in an instance method).
+If the chain is halted in a before callback, the email will *NOT* be delivered and no other callbacks will be invoked (either any after callbacks or any remaining before callbacks).
+If the chain is halted in an after callback, the email will have already been sent and all before callbacks would have run, but any remaining after callbacks will not be invoked.
+
+class FooMailer < ActionMailer::Base
+ before_deliver do |mail|
+ halt_callback_chain if invalid_mail?(mail)
+ end
+
+ after_deliver :abort
+
+ def abort(mail)
+ self.class.halt_callback_chain
+ end
+end
== Anything Planned?
First, I would like to clean up the code a bit (extract most of the logic into a separate module, etc.). I apologize for the messiness of the code, at the time I wrote it I just wanted something "that worked" and I was pressed for time.
In terms of features, I would like to do at least three more things with this:
1) Make it a proper plugin
-2) Allow the before_deliver method to halt sending the email
-3) Add an around_deliver callback which would allow around advice.
+2) Add an around_deliver callback which would allow around advice.
+3) Add ability to skip filters
+4) Add ability to prepend filters in the chain
Feedback is welcomed.
View
20 action_mailer_callbacks.rb
@@ -1,4 +1,6 @@
-class ActionMailer::Base
+ass ActionMailer::Base
+ cattr_accessor :callback_chain_halted
+
def self.before_deliver(*args, &block)
delivery_callback :before, *args, &block
end
@@ -39,9 +41,11 @@ def create_with_method_stored!(method_name, *parameters)
alias_method_chain :create!, :method_stored
def deliver_with_callbacks!(mail = @mail)
- invoke_delivery_callbacks(:before, mail)
- deliver_without_callbacks!
- invoke_delivery_callbacks(:after, mail)
+ self.class.callback_chain_halted = false
+ if invoke_delivery_callbacks(:before, mail)
+ deliver_without_callbacks!
+ invoke_delivery_callbacks(:after, mail)
+ end
end
alias_method_chain :deliver!, :callbacks
@@ -50,6 +54,8 @@ def invoke_delivery_callbacks(type, mail)
method = mail.instance_variable_get '@method_name'
callbacks.each do |callable, options|
+ break if callback_chain_halted
+
only = [].push(*options[:only]) if options[:only] && method
next if only && !(only.include?(method.to_s) || only.include?(method.to_sym))
@@ -62,6 +68,12 @@ def invoke_delivery_callbacks(type, mail)
send(callable, mail)
end
end
+
+ return !callback_chain_halted
+ end
+
+ def self.halt_callback_chain
+ self.callback_chain_halted = true
end
end
View
51 action_mailer_callbacks_test.rb
@@ -3,6 +3,7 @@
class ActionMailerCallbacksTest < Test::Unit::TestCase
class TestMailer < ActionMailer::Base
cattr_accessor :befores, :afters
+ cattr_accessor :before_deliveries, :after_deliveries
def always; end #no-op
before_deliver do |mail|
@@ -45,7 +46,29 @@ def not_in_only; end #no-op
after_deliver :only => :in_only do |mail|
TestMailer.afters << :after_only
end
+
+ def halts; end
+ before_deliver :only => :halts do |mail|
+ TestMailer.befores << :halts_before
+ halt_callback_chain
+ end
+ after_deliver :only => :halts do |mail|
+ TestMailer.afters << :halts_after
+ raise 'this should never happen if the chain is halted...'
+ end
+
+ # we can test this by checking the deliveries in the before and after callbacks
+ def ensure_before_and_after; end
+ before_deliver :only => :ensure_before_and_after do |mail|
+ TestMailer.befores << :ensure_before
+ TestMailer.before_deliveries = ActionMailer::Base.deliveries.dup
+ end
+ after_deliver :only => :ensure_before_and_after do |mail|
+ TestMailer.afters << :ensure_before
+ TestMailer.after_deliveries = ActionMailer::Base.deliveries.dup
+ end
+
def template_path
"#{RAILS_ROOT}/test/fixtures/test_mailer"
end
@@ -58,6 +81,9 @@ def render(opts)
setup do
TestMailer.befores = []
TestMailer.afters = []
+ TestMailer.before_deliveries = []
+ TestMailer.after_deliveries = []
+ ActionMailer::Base.deliveries = []
end
def test_invoke_before_with_block
@@ -129,5 +155,28 @@ def test_not_invoke_after_when_in_except_option
TestMailer.deliver_in_except
assert !TestMailer.afters.include?(:after_except)
end
-
+
+ def test_should_halt_chain_when_callback_halts
+ TestMailer.deliver_halts
+ assert TestMailer.befores.include?(:halts_before)
+ assert !TestMailer.afters.include?(:halts_after)
+ end
+
+ def test_should_not_halt_chain_when_previous_chain_halted
+ TestMailer.deliver_halts
+ assert !TestMailer.afters.include?(:after_block)
+
+ TestMailer.deliver_always
+ assert TestMailer.afters.include?(:after_block)
+ end
+
+ def test_ensure_before_filter_called_before_method
+ TestMailer.deliver_ensure_before_and_after
+ assert TestMailer.before_deliveries.empty?
+ end
+
+ def test_ensure_after_filter_called_after_method
+ TestMailer.deliver_ensure_before_and_after
+ assert !TestMailer.after_deliveries.empty?
+ end
end

0 comments on commit 9b11a98

Please sign in to comment.