Helping out ActionMailer
by Sean Cribbs
ActionMailer
is the “red-headed step-child” of Rails. Is it a model?
Is it a view? Is it a controller? It’s really none of the above, but
sits in no-man’s land. In addition it’s usage pattern is kind of
strange, you call the class with create_
or deliver_
in front of
the method name that represents the message you want to send!
That craziness aside, I encountered a particular problem today that
ActionMailer
made rather opaque. The solution is a peek at how you
can bend Rails to your will using the power of Ruby. GiftLasso has
some frequently used helpers in the view that generate parts-of-speech
(primarily pronouns) based on the gender of the person in question. I
lumped them all in a helper called, conveniently, GenderHelper
. Now
I wanted to make use of these in my ActionMailer
class so we can say
things like “Joe added an iPod to his wishlist” or “Jane purchased a
shirt for herself”.
Naturally, I thought, “The ActionMailer
views should have access to
methods on the class generating them. So I’ll include the helper.”
No dice. Unit tests failed left and right. Whenever it doesn’t “just
work”, I head to the source. How better to determine how it works?
Digging through ActionMailer::Base
, I found this snippet at the
bottom:
Aha! So it actually uses ActionView
! That’s our attack point.
Now the first temptation would be to open ActionView::Base
and muck
around with it, but I don’t want to break anything that the
controllers depend on. Luckily, Ruby has the ability to extend the
class of an instantiated object (aka the singleton class). We also
have at our disposal the beautiful alias_method_chain
from
ActiveSupport
. So what we’ll do is intercept the creation of the
ActionView
object and extend it before it renders anything. Here’s
what I came up with:
You can use this technique to add any helpers you want to ActionMailer.
The technique is part of a larger pattern called prototype-based programming. Ruby lets you freely mix prototype-based and inheritance-based patterns to achieve your goal. Javascript also uses prototype-based programming, hence the name of the library Prototype.