I’ve mentioned before that I have a little beef with Rails’s ActiveRecord. The symptom is that it’s difficult to test well, and the problem is that it mixes business and persistence logic.
I discovered another symptom today. This is something that has bothered me for over a year, and it never actually hit me until today.
Let’s say we’ve got the following Person class:
In the fictionary app in which a Person lives, one business rule is that each Person must have a name. Rails lets us clearly express that with validates_presence_of.
The problem is that this business rule is not enforced until the record is actually saved. This means that
is a perfectly valid sequence. I don’t know about you, but I find that downright offensive. A Person can’t correctly perform its own operations, much less reliably collaborate with other objects. You can also commit such atrocities as
Objects should be initialized to a valid state. Ignoring that rule is bad. At best, you end up littering your code with all kinds of senseless error checking. At worst, you end up with strange bugs and corrupt data.
I’ve been very deep in Rails-land for the past couple years, which I think is why it just clicked tonight. I wanted to experiment with writing my app in plain Ruby and then converting domain classes to AR. I had a spec that looked something like:
When I converted it to AR, I had to toss that spec. That’s when I realized that AR doesn’t enforce the business rules until an object gets saved. That might be fine if your application is 100% CRUD, but I don’t think I’ve ever seen one of those. There’s always something happening besides just CRUD. Enforcing business rules when an object gets saved is way too late - by then it could have taken place in several interactions.
You don’t want your code to use objects that are invalid. They behave unreliably and are worthless. The easiest way to ensure that objects don’t become invalid is by making those objects enforce business rules.
It’s important to cleanly separate responsibilities. AR is focused on shuttling data in and out of a relational database, and layering some business logic on top. I wouldn’t say those are necessarily competing concerns, but it’s easy to see how they might push an object in different directions. Even just at a conceptual level, my life is easier when I’m only thinking about one thing at a time.