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 class Employee < ActiveRecord : :Base # in schema, each employee has a unique email column as :string type def employee_of_the_month? # yeah, code to determine end end class EmployeeLookupService def self . find_employee_by_email ( email ) Employee . where ( email : email ) . first end end class ReportService def self . generate_for_email ( email ) employee = EmployeeLookupService . find_employee_by_email ( email ) if employee . employee_of_the_month? # do something end end end

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 employee_of_the_month? on nil would raise exception. So how could we make sure that our code could handle this exception gracefully?

Bad way

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 class ReportService def self . generate_for_email ( email ) employee = EmployeeLookupService . find_employee_by_email ( email ) if employee && employee . employee_of_the_month? # do something end end end

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.

Good way

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 Employee :

1 2 3 4 5 6 7 8 9 class NullEmployee def initialize ( email ) @email = email end def employee_of_the_month? nil end

and I also refactor my lookup code:

1 2 3 4 5 6 7 class EmployeeLookupService def self . find_employee_by_email ( email ) Employee . where ( email : email ) . first || NullEmployee . new ( email ) end end

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 class NullEmployee # whatever there before def method_missing ( name ) nil end end

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!