The problem with ActiveRecord

The time before ActiveRecord

I may be showing my age but I remember the time before Active Record. You would either write raw SQL statements, which for a table with many fields meant a lot of tedious typing, plus the ever-present danger of SQL injection bugs. Or you could use an early Object-Relational Mapper, which required a whole load of configuration; again a ton of tedious typing that would end up out of date every time you changed the database.

Rails’ ActiveRecord library changed all that. By reading the schema from your database at run-time and then dynamically adding attributes and associations, it was an absolute revelation.

Persistence and Logic

But not all is well in ActiveRecord land.

The key criticism is that ActiveRecord models mix persistence logic with business logic. In object-orientation terms, this is “mixing concerns”. Each object should have a single role, a single reason to exist. ActiveRecord models do two things.

So what you may ask?

Well, it means that testing your business logic without a database becomes impossible. This may not matter to you. But it does slow your tests down (and I’ve had many an app where the tests slowly got out of date because we just didn’t bother to run them that often). And it can make it difficult to reuse certain bits of logic elsewhere.

Codd and all that

But arguably, that’s not the biggest problem with ActiveRecord.

Relational databases have been around for decades. They’re practically the coelacanths of the computer science world – just hanging around since time began, going about their thing in the background.

Relational database theory is built on a whole series of mathematical principles. Understand those and you can make much better use of your database. How to normalise, when to denormalise, how set theory applies to your SQL statements – all this stuff is vital, especially as your database grows and you have to keep the thing operating efficiently.

But object-orientated design is different to relational database design. ORMs, like ActiveRecord bridge this gap, but it’s also lead to a generation of programmers who start from the objects and apply them to the database, often without understanding the performance implications of those decisions. It’s also lead to the growth of NoSQL databases, which aren’t relational – this is the right choice in many cases, and they do fit better with object-orientated code, but there are many many cases where a relational database is absolutely the right choice. Especially when the data is structured or large-scale reporting is required.

Separating design, persistence and logic

What we need to do is keep the database design clean, but still accessible from the object code. We need to separate the persistence from the logic.

Which is why my applications all use a “Command Layer”. This is a series of objects that contain just logic. For example, if you had a booking system, and you could only cancel a booking on the day before, you could implement this in traditional Rails as follows:

This is relatively simple. But imagine if there were lots of different conditions – you can only update your luggage within certain criteria, you can only change the passenger details 7 days beforehand and so on. A whole series of validations and callbacks filling up your model; it gradually becomes harder and harder to understand and you need to keep track of what is triggered when to see what’s going on.

Compare that to this:

All the logic is in one place, all the persistence is in another (the only exception is the validation on reservation_date, but that is equivalent to marking the field as “not null” in the database (and you should do that as well, as ActiveRecord is subject to race conditions, which are a git to debug).

Equally important; while callbacks are triggered at various points of time, this command shows what it does sequentially. Again, in a trivial example like this, it doesn’t really matter. But in a large application, having everything available at a glance makes a real difference.

In addition, DeletesBooking can easily be tested – it doesn’t require an actual database-ready Booking object, it just needs some sort of fake object, a double, that has a reservation_date and a destroy method.

And if we have other logic that applies during updates (luggage, passenger names) they sit in the UpdatesBooking class – or the ChangesPassengerName class – or whichever piece of named logic makes sense.

Finally, if you squint hard enough, you may just notice that what we’ve done is define a data-structure and then a process – you could even call it a procedure. We’ve taken all that relational database knowledge and reformulated it into an easy to understand, easily testable, object-orientated form. The best of both worlds.

Do you know what to do but not how it works?

Ever wanted to understand why Rails views work the way that they do? Why variables from your controllers are visible inside your views?

Sign up below to get a free 5 part email course on how Ruby on Rails view rendering works and gain a deep understanding of the Rails magic.

We will send you the course, plus the occasional update from this web-site. But no spam, we promise, and it's easy to unsubscribe