Rails and Referential Integrity
So in the course of working on that new Rails-based web site for our silly obsessive games nights, I ran into a problem:
In an effort to maintain the data from the old, PHP-based app, I’m starting with the original database schema and using Rails migrations to bash it into shape, adapting it to the Rails conventions and the like.
Anyway, things are going along well, but I ran into a problem: The database has no referential integrity.
Here’s an example. I have a table of Drivers, a table of Races, and a “join” table called RaceEntries, where row has a pointer to a Driver and to a Race. But there was nothing in the database, or in the code, which would prevent someone from deleting a Driver who was already entered into a Race. When that happens, you can get unexpected results when doing things like computing which driver won the most races.
This wasn’t really a problem when I was the only one updating results on the site, and I could go in directly with the mysql command line and fix things. (OK, actually, that was a problem, but one I could live with.) But given that one of the goals of recoding the site in Rails is to allow others to enter results, I needed it to be more bulletproof. So, off to the interwebs!
Turns out, there’s a big debate in and around the Rails community about the best layer at which to do this. Databases have support for this internally, by defining foreign keys in the database. So the smarts could easily go there.
One problem: Rails isn’t designed that way. None other than David Heinemeier Hansson, the original founder of the project, is firmly against it. (The permalinks are broken, so you’ll have to scroll down to the entry entitled “Choose a single layer of cleverness”.)
His reasoning is that these things are “business logic” - and your business logic should be in the application code, not the database. That’s all well and good - except it’s not that well documented. The current Rails Guide for 3.2 don’t really talk about it, and the section on the :dependent option is incomplete, not even mentioning the option to prevent deleting the object if it’s referenced from another object.
I was finally able to find it in the API docs for has_many, but it wasn’t easy. So now, in good open source fashion, I have a new to-do to write an update for the guide explaining all this…