Organize Your Rails Mailers (and Observers, Sweepers, etc.)

Posted by Lance Ivy Thu, 21 Aug 2008 16:25:00 GMT

I’m planning a serious of Rails posts covering some conventions that I’ve come to really to appreciate in the applications I develop. I’m going to make this first one easy on myself—let’s talk about organization!

How Rails Organizes

The way Rails organizes application code was deeply satisfying for me, coming from my home-brew PHP background. The app/ folder with app/controllers, app/models, and app/views really helped me embrace the MVC paradigm. Love it!

But when I start adding email, I get a little twitchy—Rails files the ActionMailer classes under app/models, and the email views under app/views. I get this. I understand this. It still makes me a little twitchy.

Then I start adding observers, which also go in app/models even though they’re really not models. But they’re related to the models, so that’s got to count for something, right? Eh.

And how about sweepers? Do they really even have a home? Their heritage is so confused.

But do you know one of the worst parts of all of this? Having user.rb and user_mailer.rb and user_observer.rb breaks my shell’s tab-completion! Noez!

How I Organize

This is simple—embrace config.load_paths and widen your app/ directory. I like to create app/emails, app/observers, and especially app/sweepers. This really helps me find code faster, because when I’m navigating my folders looking for an observer, the first thing in my mind is “category: observer”, not “something related to models”. So navigating to app/observers is much more intuitive.

How does this happen? Again, simple.

<div class="codetitle">config/environment.rb</div>Rails::Initializer.run do |config|
  ...
  %w(observers sweepers emails).each do |dir|
    config.load_paths &lt;&lt; "#{RAILS_ROOT}/app/#{dir}"
  end
  ...
end

Then just start moving your code in there. Rails will find it.

We’re Not Done With Mailers

You’ll want to do one more thing to organize your mailers. Without this step, you’ll have only cleaned up your app/models directory, and you’ll still have templates scattered in app/views.

First, create an application mailer (just like the application controller):

class ApplicationMailer < ActionMailer::Base
  self.template_root = File.join(RAILS_ROOT, 'app', 'emails', 'views')
end

Now make all your mailers inherit from ApplicationMailer, and move your email template into app/emails/views (e.g. your mailer is app/emails/user_mailer.rb and your templates are in app/emails/views/user_mailer/).

And for a bonus? You now have a place to add custom helpers and email setup methods to really streamline email setup for your application.


Comments

  1. Avatar
    Daniel Lucraft about 22 hours later:

    I love the app/emails/ (or app/mailers/ as I have called it) but I’m not sure about the app/emails/views/ directory. Wouldn’t that imply the existence of app/controllers/views/ to be consistent?

    Thanks for wrapping this up into a clear set of changes.

  2. Avatar
    Lance 1 day later:

    I think you can have app/emails/views without implying app/controllers/views. This change, like moving mailers out of the app/models directory, is more about putting things where you’ll find them than about rigid categorization guidelines.

    Oh, perhaps it would make more sense if emails were a top level directory? Nah, but I do like having all the app-specific code in app/.

  3. Avatar
    Darragh Curran 5 days later:

    I pretty much do the same – (app/mailers instead of app/emails).

    Your suggestion of using ApplicationMailer is a good option to contain code shared by mailers – and follows the pattern used for controllers.

    But you can also configure the template root in your environment.rb config block with something like this.

    config.action_mailer.template_root = "#{RAILS_ROOT}/app/mailers/views"

    I wonder app/observers|sweepers|mailers will be defaults at some stage.