We required a simple templaing system for our emails and a way to associate these templates with mailer actions. At first, I though getting the name of the action in Mailer (ActionMailer::Base), but it is not possible — ActionMailer is one of a kind, it’s not a controller and does not have the functionalities of ActionController::Base.
The templating system actually needs to allow the user to access attributes from models of the application and also be able to insert html “widgets” (pieces of hmtl data), both using tags. What we did was to create a EmailParser helper class (which could be a module, but this seemed simpler) and some singleton methods to parse the tags. For each tag, EmailParser checks in the passed in model instance and calls it’s content string as a method, to get the attribute. There is a security risk here, but since this is available only in the backend of the application, we can set up to solve this in a later iteration. Then you can also pass a “view helper”, which is supposed to be a class or module with methods that return complex html structures. We plan on extending this to make it more flexible and robust, or maybe use redcloth or another existing solution, but this does the job for now.
We have several points in the application where we send emails. We need a way to identify each of these points. The first alternative is obvious – stick to ActionMailer and use the action defs. And this is what I did, I created a MailerAction model that has a name of an action def from the main application mailer (app/models/mailer.rb). This model belongs_to EmailTemplate, meaning that each action is associated to a template. An EmailTemplate, in its turn, may be associated to many actions, so EmailTEmplate has_many :mailer_actions.
So here’s a sample of how a mailer action def would look like:
def mailer_action(model_instance, subject, sender)
message = MailerAction.find_by_name('mailer_action').email_template.template_for_object(model_instance)
from sender
recipients account.email
subject subject
body :model => model_instance, :message => message
template 'message' #explicitly specify the template to use here!
end
Note the template method from the DSL, it says tells ActionMailer to use a template with a name different from the action name. This is crucial for this system to work.
The bad thing here is that I have to repeat this logic for every mailer action. Of course, I could encapsulate and abstract some of it in a module (helper) or a simple private method, but I don’t think the cost-benefict would worth it in the current iteration (since there are not that many email actions using the new templating system right now).
Now, we use only one erb template that then renders the message for us:
app/views/mailer/message.html.erb
<%= @message %>
Not the most elegant solution, but works
In my next post I will explain how the EmailParser class works, so you can get the missing piece to understand how this particular feature works.