company-logo

ActionMailer Callbacks: In the Spirit of ActionController Filters

One of the most useful features of ActionController is the ability to add filters before, after, or around actions.  This tool is made even more powerful by the ability to chain filters together.  Allowing an AOP approach is indispensable for addressing cross cutting concerns (or simply separating concerns), and is one of the things which makes a framework valuable to a developer (Spring is an excellent example of this).

A while back, I had a requirement to persist a record of which email addresses were sent an email through the system.  I expected to find callback support for ActionMailer, but was surprised to find that it didn’t exist.

I had three options: put the logic inline in each ActionMailer method which is not DRY and muddles concerns, put the logic in the ActionContoller as filters which break encapsulation in terrible ways (and is at the wrong layer), or extend ActionMailler to allow callback methods.

I choose the latter…


I created a simple plugin which allows you to add before and after deliver callbacks to ActionMailer, and constrain them to certain methods using :only and :except options ala ActionController‘s filters.

You can add the plugin by executing the following in your rails app directory:

script/plugin install git://github.com/AnthonyCaliendo/action_mailer_callbacks.git

Here is a snipet from the readme:

There are 2 main ways to define a callback. In each case, the callback method/block is passed the mail object 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



== What About Halting the Chain?

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

I wanted to keep usage similar to how filters are handled in ActionController, so I decided against using the return value of the callback to halt the chain. Instead, you explicitly halt the chain similar to how rendering or redirecting halts a before_filter chain in a controller.

I am hosting the code on github at http://github.com/AnthonyCaliendo/action_mailer_callbacks/tree/master. I was time constrained when I wrote this, but I will be cleaning up the code once I get a chance (I promise!).

I wrote this code months ago (just now got around to blogging about it and releasing it to github), but while researching this blog post I saw that someone else came up with a similar plugin at http://github.com/kelyar/mailer_callbacks/tree/master. Kelyar’s plugin seems more limited than the one I am providing (plus, there are no tests… tsk tsk!), but it is cleaner and simpler. It may be worth taking a look at that as well.

  1. Pingback: Ennuyer.net » Blog Archive » 2009-01-22 - Today’s Ruby/Rails Reading

  2. Pratik Reply

    We talked about a few times to add some callbacks to Action Mailer. But none of us had a concrete use case, so that has been getting delayed forever. But it looks like you have a very good use here. Why don’t you submit a patch ? Or even start with mailing the core list – http://groups.google.com/group/rubyonrails-core – to check if more people are interested or not.

    Thanks!

  3. Anthony Caliendo Reply

    Yeah, I can definitely submit a patch to rails core. I will first add around_deliver, prepend, and skip logic to this plugin, and then create a patch (so the patch is “complete”).

    Thanks for the suggestion.

  4. Massimiliano Pepe Reply

    It’s grat! You saved me a lot of time!! Thank you.

  5. marc mowger Reply

    Awesome exactly what I was just looking for!

Leave a Reply

*

captcha *