Rails 2.3 and theme_support part 2: ActionMailer

Stage 2 of fixing up theme_support for Rails 2.3 was making sure that ActionMailer picked up themed templates (for stage 1 information see here). That’s something I’d not quite cracked in the 2.2 version, so starting afresh with 2.3 forced me to spend the time to look through the full render path and figure out what was going on.

ActionMailer is a little more complicated than ActionView in that there are multiple routes of entry (ways of sending emails) and each email can have multiple templates associated with it to allow for multipart email. But at the core of it all is the ActionMailer::Base#create! method. This executes the specific method that populates the mailer variables (ie. the code you actually write in your mailers) and then uses Dir.glob to look for appropriate templates for this email:

Dir.glob("#{template_path}/#{@template}.*").each do |path|
  template = template_root["#{template_path}/#{File.basename(path)}"]

  # Skip unless template has a multipart format
  next unless template && template.multipart?

  @parts <  template.content_type,
    :disposition => "inline",
    :charset => charset,
    :body => render_message(template, @body)
  )
end

The initial patch provides a couple of ways to specify the theme for an email. I’m mainly using the approach of specifying self.current_theme within a mailer method. eg:

class UserMailer < ActionMailer::Base
  def activation(user)
    setup_email(user)
    self.current_theme = user.site.theme
    @subject    += 'Your account has been activated!'
    @body[:url]  = "http://www.catapultmagazine.com/"
  end
end

With that in place the next step was to patch ActionMailer::Base#create! so that instead of just looking at the main template_path it looked first in the relevant theme folder and then in the main template folder. So the code above becomes:

tpaths = []
tpaths < < File.join(RAILS_ROOT, "themes", self.current_theme, "views", mailer_name) if self.current_theme
tpaths << template_path

tpaths.each do |tpath|
  Dir.glob("#{tpath}/#{@template}.*").each do |path|
    template = template_root["#{tpath}/#{File.basename(path)}"]

    # Skip unless template has a multipart format
    next unless template && template.multipart?

    @parts < template.content_type,
      :disposition => "inline",
      :charset => charset,
      :body => render_message(template, @body)
    )
  end
  break if @parts.any?
end

The keen-eyed among you will notice that this means you can’t have, say, your HTML part themed and your plain text part in your main app/views folder. There would be ways around that, but this seemed the cleanest approach to take.

UPDATE (5pm): I’ve now got layouts working too. More on that over here.

Tags: , ,

6 comments

  1. I get a
    ActionView::MissingTemplate: Missing template user_mailer/signup_notification.erb in view path app/views
    error. My current_theme is set to ‘nil’ and the files are in place:
    /app/views/user_mailer/signup_notification.text.plain.erb..

    Any idea?

  2. I saw a problem like that in my older 2.2 version of the plugin but haven’t seen it since.

    The place to start looking is in theme_support/lib/actionmailer_ex.rb around like 40 to see what details are being captured for the template value.

    I’d be happy to take a look as I’m very keen to make sure we get this working solidly. Email me at james@jystewart.net if you need a hand.

  3. Thanks. I will give it a look and email you for info if needed. Thanks!

  4. Still not able to use the above for themed mails. And do you know how to make this work with localized views (in Rails 2.3’s I18n)..

  5. @joost – I got your emails, thanks. I haven’t been able to spend any time on the issue yet as I’ve been ill the past few days.

    Will look at them over the next couple of days and try to work out what’s going on. In the meantime, I’m happy to accept any patches that you figure out.

  6. Thanks. Did not manage to create a patch yet 🙂