The Active Record pattern combines data access and domain logic. Over time, an Active Record class can accumulate a large amount of query methods. Moving these methods into a separate object can result in smaller, more cohesive domain objects.
Here’s an Active Record class with a few queries (for simplicity, there’s only two):
Let’s move these queries to another object:
Instead of naming this object something like ILoanRepositoryImpl
or RdbLoanDAO
, we used a word from the domain. Words like “repository” and “DAO” are implementation details. By including vocabulary from the domain, we create a richer domain model.
I used a module because there’s no state; only behavior. Without state, you only need one instance. Using a class would communicate that you could instantiate a Lender
, which would be useless. When you only have behavior, a module is all you need.
One tradeoff from moving these queries out of Loan
is that Lender
is now dependent on some of Loan
‘s attributes (specifically, expires_on
and repaid
). However, this dependency is ok because these attributes are part of Loan
‘s public interface.
Test this module using mini-integration tests. Instead of mocking out Loan
, rely on the real Loan
object and its database. Test it as if this method was on Loan
.
Here’s another example (again, for simplicity, this example only contains one query):
Let’s move this query to a new Bank
object:
And one last example:
We can move this into a TelephonyProvider
:
Use this refactoring when the majority of an Active Record class consists of queries. The key to this refactoring is finding a good name for the new object. Take your time and don’t fall back on old habits. Avoiding pattern names in class names is a good way to get fellow developers on board with this refactoring.
This refactoring only addresses persistence concerns at the class-level. Active Record objects still have instance-level persistence methods (e.g., #save, #update_attributes, #destroy). By only refactoring class behavior, you can gradually move away from Active Record.