This started out as a long comment on Brian Kotek's recent post about the ActiveRecord pattern. I figure that while I've still got some Rails-ish types irked at me, I may as well go ahead and blog a bit more about why I don't like the datacentricness of the ActiveRecord approach.

Before you fire up your flame generation script, let me say this: ActiveRecord is super-productive when you need to allow someone to edit a relational model. It doesn't, however, suit the kind of OOD-backed development I do. This post is about why.

An ActiveRecord implementation, according to Martin Fowler, should have properties that "exactly match that of the database: one field in the class for each column in the table."

Ok, that's straight-up simple.

Let's take this thought further, applying rampant use of the "convention" principles of Rails'. If you take a look at the RoR Wiki, you'll see that there are a number of "magic field names" that can be added to a database/AR model to automagically populate data and even add behavior (such as tree-like abilities via a magic name and an acts_as_wibbleWobble statment).

Ok, that makes it really easy to do something like update "created" and "updated" dates on insert/update.

From a Rails perspective, here's what it'd look like to have a Contact that's both acting as a tree (via adjacency list, aka "parent-child") and using the created/updated_on bits:

Ok, still simple. It makes sense.

It breaks down, miserably, when spread across a large object model, especially when changes occur. Let's pretend you're working in a good-sized CRM implementation with 40-50 domain objects that get persisted with audit information, and 3-4 trees. That's a lot like my main project right now (except it isn't a CRM system).

Problem Scenario One

Pointy-haired boss comes in and says "That audit information isn't enough. Storing when something was changed isn't what I need - I need to know who changes it."

No problem. You go to the Rails wiki and find out this is common. So common, in fact, that code is provided to do it.

You do what it says, then add the created_by and updated_by columns to your 40-50 tables (no way to avoid that). Rails'll pick up the changes and update your....40 or 50 domain objects?

Wait, why should one simple request to change one aspect of behavior impact this many domain objects? That should set off a warning sign about ActiveRecord. A big red sign.

From the perspective of OO design (not data modeling, which is what ActiveRecord pushes), we're really interesting in spreading a common set of audit information across a domain. In fact, it'd be better to express it as this, moving created_by and such out of our Contact:

It just got easier. Now, instead of checking for the presence of a field (sloppy!)...

model.find(self[:created_by])

...we can use that wonderful thing called polymorphism to see if we're dealing with an IAuditable:

if (model is IAuditable) {// populate model.auditInformation with whatever we want }

That's a lot cleaner: one change (such as adding who changed something) changes one class, and because we use OO concepts instead of relational concepts, we get all-around cleaner.

Problem Scenario Two

The first problem was kind of elementary. Solving trees is a more complicated task.

Rails does, to its credit, make it fairly easy to create adjacency and nested set trees.

However, if we wanted to change our tree-based objects from adjacency to nested set, we'd be in the same trouble as before: we'd change all our domain objects implementations because we swallowed the Rails kool-aid and repeatedly spread the same behavior across multiple domain objects instead of drinking the OOD brew of isolating what changes, identifying interfaces, and encapsulating behavior.

Here's how I'd approach a system that may change between tree behaviors:

Again, we isolate what may change (the approach to tree implementation), determine a contract (interface), then use composition to give us a flexible, OO domain model, rather than an elementary data model reflected into an API.

(Side note: this is a vanilla implementation of the Strategy Pattern.)

Consequences of ActiveRecord

Yes, ActiveRecord has consequences. Followed blindly, we've seen that it leads to poor OO design by favoring data model reflection over interfaces, encapsulation, and all that good OO jazz. I'm not saying there aren't ways around it (I'm sure John Paul Ashenfelter is already writing his comment ;). [Hi John, buy you a beer at MAX?]), but does it seem realistic that most people using ActiveRecord are going to stop, think about what they're doing, and refactor?

Going out on an editorial limb, it's crossed my mind that one of the causes of the Java -> Ruby (on Rails) shift might be that many Java "developers" didn't get OOP or OOD to begin with and RoR lets them change to something that helps them build CRUD apps by providing massive relational crutches. While these crutches could be useful in good hands (I have a hard time believing that companies like Fowler's ThoughtWorks use Rails inappropriately!), are they just helping others limp along and avoid good design?