In my application, I bump to Exception when trying to delegate a method to an unfound ActiveRecord instance. This poses two issues for me:
- Hard to write test for you have to set up fixture/factory correctly
- Not a good user experience to see error on production
I tackle this with NullObject pattern to provide a graceful fallback.
Firstly, let me show you my code:
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
I have a Report which generates report for an employee with matching email. As you can in the above
code, if the employee could be be found by email,
EmployeeLookupService.find_employee_by_email would yield a
nil and calling
nil would raise exception. So how could we make sure that our code could handle this exception gracefully?
Well, there is one quick bad way, that is to add a addtional presence check for employee, so:
1 2 3 4 5 6 7 8 9 10 11
As you can see, I add
if employee clause to ensure the presence of employee. What’s wrong with this way? I find many Rails developers code this way, but to me it is not good enough. In term of OO design, I expect
EmployeeLookupService.find_employee_by_email returns me an object which responds to
employee_of_the_month? consistently instead of returning me a
nil. Furthermore, it is not the responsibiltiy of
ReportService to do presence checkup.
Here is how I refactor the code, I use NullObject pattern to create a new class, called
NullEmployee and ensure that this class has the same interface as
1 2 3 4 5 6 7 8 9
and I also refactor my lookup code:
1 2 3 4 5 6 7
Let’s digest what I did above, I make
EmployeeLookupService.find_employee_by_email to create a new instance of
NullEmployee class if not found and this
NullEmployee#employee_of_the_month? always return
nil. Now we do have a consistency in returned employee object. And testing it would be much more pleasant, we do not have to care about setting up this employee fixture correctly, we could simply stub
employee_of_the_month? which makes testing faster and reduces coupling.
Now, there is one caveat with this NullObject patter and ActiveRecord, what if we call an ActiveRecord API on this NullEmployee instance. Well, we could add all ActiveRecord API into our class and return nil, right? No, I must be kidding in saying that. There are hundreds of them, our class would look messy. The elegant solution is provide graceful fallback for missing methods by implement
method_missing call. Here is the code:
1 2 3 4 5 6 7 8 9
Now calling a missing method won’t yield any exception.
That’s it folks. I hope you enjoy it and please never keep learning and remembering these thumb rules:
- Single Reponsibility
- Interface Consistency
- Never mix persisence layer with business logic layer
- If hard to write test, your code might be wrong
See you in the next article. Peace!