This post first appeared on the Bloc blog in January 2015
As code bases grow, the need to refactor grows with it. A couple weeks ago, I saw an opportunity to refactor Bloc’s scheduled emails, which we activate in a Rakefile.
The Rakefile looked something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Plus several more notifications.
I wanted to test the code in this file, but as it is, it’s not very friendly to testing. However, there was a pattern among all these scheduled emails – grabbing specific enrollments and performing certain email actions. Sounds like a great use case for a service object.
Here was my first go-round:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
This was a bit better. I could easily test this class. But it seemed a little repetitive to me. All I’m doing is iterating over the enrollments and then sending a bunch of emails. For some I use more than one mailer, but the overall goal is the same: take some enrollments and send some specific emails.
Also, if you’ve ever read Practical Object Oriented Design in Ruby, you’ll notice this class doesn’t manage dependencies very well. There are at least a couple different classes in the
So I looked for a way to abstract all of this information out in such a way that my
MassEnrollmentMailer was completely unaware of any other class. This is easily done with dependency injection. My goal was to make a class that did only one thing, deliver emails, and it would use whatever mailer classes and email methods I gave to it.
Here’s what this new class, which I called simply
MassMailer because it would be able to perform mass emails on anything, looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
This is now a much simpler, more versatile, and easily testable class of code.
I initialize the class with the collection of enrollments as well as an array, which I call
send_emails! method, I loop over the array and the enrollments, calling the
send_email! method, which takes the mailer class, the email method, and the object needed to be acted upon.
send_email!, I can use the
send method to call the email method with the object as its argument on the mailer class.
The Rakefile now looks like this for each task:
1 2 3 4 5 6 7 8 9 10 11
You might notice that I chose to use a nested array for
mailers_and_emails. At first, I experimented with a hash, in which the keys were the mailer classes, and their values were the methods. I then used
Object.const_get in order to convert the key into a constant. While this is certainly a valid way of doing this, metaprogramming like this can often complicate debugging. The nested array allows me to directly pass in the class and allows the same ease of iteration.
MassEnrollmentMailer only does one thing,
deliver_mail!. It doesn’t know or care about what mailer classes it needs, it just needs the classes and methods to be given to it.