The prevailing wisdom in the Ruby world is that using eval
is bad. It’s brittle, hackish and inflexible – if you find yourself using it, it’s usually a sign you’re doing something wrong.
Well, I agree, but the keyword is “usually”. Sometimes using eval is a lot better than any alternatives, and it can help work around problems in, say, other people’s code, where the design decisions they made stop you doing what you want. I avoided using it for a long time, maybe based on my acceptance of the consensus opinion – but recently I’ve had cases where it’s the “lesser of two evils”, and I wanted to share one with you.
The example is Rails’ horrible mailing system. It’s one of the worst parts of Rails – but let me temper that by saying it’s a hard problem and difficult to see how else they could have done it. A Mailer in Rails is kind of a pseudo-model black box that waits to be sent a list of parameters, then inserts some of them into a specific text file, then sends that text using the remainder of the parameters.
All very well and good so far. But the problem with this is when you want to have mail in multiple languages. So you don’t just have
Notifier.deliver_welcome_mail(params)
you have
Notfier.deliver_welcome_mail_eng(params)
Notfier.deliver_welcome_mail_jpn(params)
Notfier.deliver_welcome_mail_spa(params)
etc. All with corresponding entries in the model, and matching text files.
Now, I don’t know any way to get around the need for those text files and entries in the model that doesn’t involve writing some huge text generator (bad) or hacking Rails itself (even worse). But we can at least improve on the statements used to call these deliveries.
Here’s an example of what I used to have:
if .language_iso == 'zht'
Notifier.deliver_invite_zht(, , subject)
elsif .language_iso == 'zhs'
Notifier.deliver_invite_zhs(, , subject)
elsif .language_iso == 'jpn'
Notifier.deliver_invite_jpn(, , subject)
else
Notifier.deliver_invite_eng(, , subject)
end
That’s a shortened version of what can be much longer if
or case
statements (this is pretty old code..). But you can see this gets very nasty, very quickly.
Using eval we can change that to:
eval("Notifier.deliver_invite_#{.language_iso}(, , subject)")
Yes, it’s still nasty. But it’s one line of nastiness as opposed to 10 (or many more). I think this is one example of a case where using eval is definitely better than the alternative.
Next, I’m experimenting with trying to reduce the horrendous duplication in the Mailer pseudomodel itself.