Permalink
Browse files

initial commit of version 1.0 action_mailer_callbacks

  • Loading branch information...
0 parents commit b079f1769fab82916c138c7824f77092a9b64b7c @AnthonyCaliendo committed Jan 21, 2009
Showing with 266 additions and 0 deletions.
  1. +66 −0 README
  2. +67 −0 action_mailer_callbacks.rb
  3. +133 −0 action_mailer_callbacks_test.rb
66 README
@@ -0,0 +1,66 @@
+= ActionMailer Callbacks
+
+Version 1.0
+
+== Installation
+
+Drop "action_mailer_callbacks.rb" into either your "lib" directory.
+Drop "action_mailer_callbacks_test.rb" into your "test/units" directory.
+
+In the future, this will be made a proper plugin to make installation easier...
+
+== What Is It?
+
+ActionMailer callbacks provides basic callback support for ActionMailer. It providers similar functionality to before and after filters available in ActionController.
+
+== How Do I Use It?=
+
+There are 2 main ways to define a callback. In each case, the callback method is passed the mail as the only argument.
+
+You may define a callback using a block:
+class FooMailer < ActionMailer::Base
+ after_deliver do |mail|
+ ...
+ end
+end
+
+You may also define a callback using a symbol/string for a method name:
+class FooMailer < ActionMailer::Base
+ before_deliver :append_advertisement
+
+ def append_advertisement(mail)
+ ...
+ end
+end
+
+Callbacks take options which can be used to define which mail types (i.e. methods) they will be applied to.
+These options take the format of *only* and *except*.
+ - An *only* callback will only be run for methods which match the passed method names.
+ - An *except* callback will be called for all methods EXCEPT those that match the passed method names
+The options can take either an array of strings/symbols, or a single string/symbol.
+
+class FooMailer < ActionMailer::Base
+ before_deliver :append_disclaimer, :only => [:email_friend, :announce_something]
+ after_deliver :notify_user, :except => :invite_user
+
+ ...
+end
+
+== Limitations
+
+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.
+
+== 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.
+
+Feedback is welcomed.
+
+== License
+
+ActionMailer Callbacks is available under an MIT-style license.
@@ -0,0 +1,67 @@
+class ActionMailer::Base
+ def self.before_deliver(*args, &block)
+ delivery_callback :before, *args, &block
+ end
+
+ def self.after_deliver(*args, &block)
+ delivery_callback :after, *args, &block
+ end
+
+ def self.delivery_callback(type, *args, &block)
+ args = args.dup
+
+ method = args.first
+ method = nil if method.is_a?(Hash)
+
+ options = args.pop
+ options = {} unless options.is_a?(Hash)
+
+ callable = block || method
+ raise 'Specify a block or method to invoke in "before_deliver"' unless callable
+
+ callbacks = class_variable_defined?("@@#{type}_deliver_callbacks") ? class_variable_get("@@#{type}_deliver_callbacks") : ActiveSupport::OrderedHash.new
+ callbacks[callable] = options.symbolize_keys
+ class_variable_set("@@#{type}_deliver_callbacks", callbacks)
+ end
+
+ private
+ def create_mail_with_method_stored
+ mail = create_mail_without_method_stored
+ mail.instance_variable_set '@method_name', @method_name
+ mail
+ end
+ alias_method_chain :create_mail, :method_stored
+
+ def create_with_method_stored!(method_name, *parameters)
+ @method_name = method_name
+ create_without_method_stored!(method_name, *parameters)
+ end
+ 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)
+ end
+ alias_method_chain :deliver!, :callbacks
+
+ def invoke_delivery_callbacks(type, mail)
+ callbacks = self.class.class_variable_defined?("@@#{type}_deliver_callbacks") ? self.class.send(:class_variable_get, "@@#{type}_deliver_callbacks") : {}
+ method = mail.instance_variable_get '@method_name'
+
+ callbacks.each do |callable, options|
+ only = [].push(*options[:only]) if options[:only] && method
+ next if only && !(only.include?(method.to_s) || only.include?(method.to_sym))
+
+ except = [].push(*options[:except]) if options[:except] && method
+ next if except && (except.include?(method.to_s) || except.include?(method.to_sym))
+
+ if callable.is_a? Proc
+ callable.call(mail)
+ else
+ send(callable, mail)
+ end
+ end
+ end
+
+end
@@ -0,0 +1,133 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class ActionMailerCallbacksTest < Test::Unit::TestCase
+ class TestMailer < ActionMailer::Base
+ cattr_accessor :befores, :afters
+
+ def always; end #no-op
+ before_deliver do |mail|
+ TestMailer.befores << :before_block
+ end
+ before_deliver 'before_from_string'
+ def before_from_string(mail)
+ TestMailer.befores << :before_string
+ end
+ before_deliver :before_from_symbol
+ def before_from_symbol(mail)
+ TestMailer.befores << :before_symbol
+ end
+ after_deliver do |mail|
+ TestMailer.afters << :after_block
+ end
+ after_deliver 'after_from_string'
+ def after_from_string(mail)
+ TestMailer.afters << :after_string
+ end
+ after_deliver :after_from_symbol
+ def after_from_symbol(mail)
+ TestMailer.afters << :after_symbol
+ end
+
+ def in_except; end # no-op
+ def not_in_except; end #no-op
+ before_deliver :except => :in_except do |mail|
+ TestMailer.befores << :before_except
+ end
+ after_deliver :except => :in_except do |mail|
+ TestMailer.afters << :after_except
+ end
+
+ def in_only; end #no-op
+ def not_in_only; end #no-op
+ before_deliver :only => :in_only do |mail|
+ TestMailer.befores << :before_only
+ end
+ after_deliver :only => :in_only do |mail|
+ TestMailer.afters << :after_only
+ end
+
+ def template_path
+ "#{RAILS_ROOT}/test/fixtures/test_mailer"
+ end
+
+ def render(opts)
+ # no-op
+ end
+ end
+
+ setup do
+ TestMailer.befores = []
+ TestMailer.afters = []
+ end
+
+ should 'invoke before with block' do
+ TestMailer.deliver_always
+ assert_true TestMailer.befores.include?(:before_block)
+ end
+
+ should 'invoke before with symbol' do
+ TestMailer.deliver_always
+ assert_true TestMailer.befores.include?(:before_symbol)
+ end
+
+ should 'invoke before with string' do
+ TestMailer.deliver_always
+ assert_true TestMailer.befores.include?(:before_string)
+ end
+
+ should 'invoke before when in only option' do
+ TestMailer.deliver_in_only
+ assert_true TestMailer.befores.include?(:before_only)
+ end
+
+ should 'invoke before when not in except option' do
+ TestMailer.deliver_not_in_except
+ assert_true TestMailer.befores.include?(:before_except)
+ end
+
+ should 'not invoke before when not in only option' do
+ TestMailer.deliver_not_in_only
+ assert_false TestMailer.befores.include?(:before_only)
+ end
+
+ should 'not invoke before when in except option' do
+ TestMailer.deliver_in_except
+ assert_false TestMailer.befores.include?(:before_except)
+ end
+
+ should 'invoke after with block' do
+ TestMailer.deliver_always
+ assert_true TestMailer.afters.include?(:after_block)
+ end
+
+ should 'invoke after with symbol' do
+ TestMailer.deliver_always
+ assert_true TestMailer.afters.include?(:after_symbol)
+ end
+
+ should 'invoke after with string' do
+ TestMailer.deliver_always
+ assert_true TestMailer.afters.include?(:after_string)
+ end
+
+ should 'invoke after when in only option' do
+ TestMailer.deliver_in_only
+ assert_true TestMailer.afters.include?(:after_only)
+ end
+
+ should 'invoke after when not in except option' do
+ TestMailer.deliver_not_in_except
+ assert_true TestMailer.afters.include?(:after_except)
+ end
+
+ should 'not invoke after when not in only option' do
+ TestMailer.deliver_not_in_only
+ assert_false TestMailer.afters.include?(:after_only)
+ end
+
+ should 'not invoke after when in except option' do
+ TestMailer.deliver_in_except
+ assert_false TestMailer.afters.include?(:after_except)
+ end
+
+end

0 comments on commit b079f17

Please sign in to comment.