DRY Internal and External Notifications using ActionMailer

I know that most Rails developers have come across the problem of notifying people by email that they have an internal message on your app’s own messaging system. Usually this is easy enough, and elegantly done in your model by using

class InternalMessage < ActiveRecord::Base   after_save :notify_person_of_new_internal_message      protected   def notify_person_of_new_internal_message      MyMailer.deliver_new_posting(self.from_person, self.message)   end end 

However, let’s consider a situation where you need duplication of data on two different processes. For example, if you would like to generate a notification message when some posts to a common forum. The quick way to do this would be by calling two separate methods in your controller, like so:

subject = "Ted Stevens has posted a new topic" body = "From Ted Stevens: " +         "'I hope you get this, the tubes have been acting up recently!'" @person.save_to_inbox(subject, body) MyMailer.deliver_new_posting(@person, subject, body) 

This requires you to generate the subject and the body of the message and use the methods to work out the logic.

However, with a little ActionMailer hackery, you can make this scheme much cleaner.

MyMailer.notify(person, :new_posting, from_person, note) 

In your model,

class MyMailer < ActionMailer::Base   class << self      def notify(person, method, *params)         new_mailer = new         new_mailer.create!(method, *params)         mail = new_mailer.mail         person.save_to_inbox(mail.subject, mail.body)         deliver(mail) if person.wants_email_notification?      end   end    def new_posting(from_person, note)      from from_person.email      subject "#{from_person.name} has posted a new topic"      @body[:from] = from_person      @body[:note] = note   end end 

Next, in views/my_mailer/new_posting.rhtml

From <%= @from_person %>: <%= @note %> 

The following three lines generate a new MyMailer object, create the email specified by the method (in this case, :notify_posting) using ActionMailer’s create! method (which in turn uses Ruby’s glorious send method), and pull out the TMail object that ActionMailer sends to the SMTP server. Conveniently, the TMail object lets you extract out the subject and body, which lets you map them to your InternalMessage class.

new_mailer = new new_mailer.create!(method, *params) mail = new_mailer.mail 

The above code generates the email, pulls out the generated subject and the body and uses it as the subject and body of the internal message. The idea is that the content of both the email notification and the internal email match up. This method does not require you to setup your subject and body. Instead, this method lets you use ActionMailer as a rendering framework for generating text/html for all of your internal messages. DRY is Good!