More Asynchronous Processing

In a previous series on using workling and starling for asychronous processing, I described how to setup background tasks. Here is a quick way to use this for emails without a lot of changes to your application. First, create lib/asynch_mail.rb:


# Makes an actionmailer class queue its emails into a workling queue
# instead of sending them sycnhronously
#
# From now on all MyMailer.deliver_whatever_email calls create an entry in
# the MailerWorker.deliver_mail corresponding queue and should be processed
# by a worker. If you still want to deliver mail sycnhronously add a bang to the method call:
# MyMailer.deliver_whatever_email!
#
module AsynchMail
  def self.included(base)
    base.class_eval do
      class < < self
        alias_method :orig_method_missing, :method_missing

        # Catch deliver method calls to turn them into asynch calls
        def method_missing(method_symbol, *parameters)
          case method_symbol.id2name
          when /^deliver_([_a-z]\w*)\!/
            orig_method_missing(method_symbol, *parameters)
          when /^deliver_([_a-z]\w*)/
            mail = self.send("create_#{$1}", *parameters)
            MailerWorker.asynch_deliver_mail(:class => self.name, :mail => mail)
          else
            orig_method_missing(method_symbol, *parameters)
          end
        end
      end
    end
  end
end

Then, create app/workers/mailer_worker.rb:


class MailerWorker < Workling::Base
  def deliver_mail(options)
    Object.const_get(options[:class]).deliver(options[:mail])
  end
end

Now, all you need to do is include AsynchMail in your mailer class.


class MyMailer < ActionMailer::Base
   include AsynchMail
end

That's it! No other changes to all your mail calls are required. All will automatically become asynchronous. If your application sends a lot of emails, I recommend you do this. There is no reason to make your users wait for an email to be sent.

The only downside with this is that now emailing happens outside of Mongrel. This means that link_to does not have access to the HOST. As a result, you can can't easily use the route helpers to generate urls. You will need to build your urls manually. At one point, I had some code that got around this, but it stopped working when I upgraded to Rails 2.x. In my app, it was easier to fix the urls than make the other work. If someone can provide this, it would great.