One of the first things that impresses a lot of developers when they first start using Rails is the almost complete lack of effort required to implement models using ActiveRecord.  Model objects are super simple to create thanks to the built-in generators, and assuming followed all the relevant naming conventions while designing your database, you’re up and running with a full-featured business object layer in no time flat.  If you’re used to another ORM framework that’s heavier in configuration – Hibernate, I’m looking in your direction! – the benefits of this way of doing things become apparent immediately.

I earned my chops developing two- and three-tiered applications that started with a database model that baked in most of the business requirements for the system as data entities, relationships and constraints.  Over that, you designed and implemented a business objects layer with all the necessary mappings to database objects (and woe unto you if there should be any major differences between the OO and relational models), and over that, you layered your user interface.  The applications often ended up looking like they were designed by a programmer – uninspired and mechanical, front-ends simply slapped onto databases – and you could expect a fun time if some damn user comes along with a requirement change that requires modification of the database.  The word for the process and the applications it produced, people, is B-R-I-T-T-L-E.  No one was happy about this state of affairs, but as developers, we passed the buck to the business stakeholders, blaming them for not being able to stick to their guns when it came to requirements.

For a long time, I didn’t really get why someone writing a serious application would ever want to use Rails migrations, and then I made a point to make a serious attempt at using them, mostly as an experiment.  I didn’t expect to find anything there that would be worth keeping up, but I thought it would be good to know something about it on the chance I ended up taking over a project that used them.  What I found, though, was a tool that has really changed the way I build applications for the better, and I use them from the very beginning of the project.  These are a few reasons why.

  1. It puts more of your codebase in Ruby.  There are a few things that everyone out there calling him or herself a developer should know, and how to put together basic SQL queries and build scripts is one of them.  SQL forms the basis for our understanding of relational databases in much the same way that language forms the basis of our thought patterns, and since most business applications require a database of some kind, I’d say that makes it indispensable.  Having said all that though, it’s also a pain in the ass to write.  (Don’t believe me?  Look at all the tools and libraries developed over the years whose only purpose was to avoid having to write SQL code.)  So while it’s an important thing to know, it’s not necessary to practice it daily, and if you can find a way to eliminate the need to write and manage SQL code in your application, especially if you can replace it with code that’s as compact and expressive as that enabled by migrations, all the better.
  2. You probably don’t need a graphical design tool.  I was hooked on these things for a long time to the point that I practically forgot how to form proper CREATE TABLE syntax.  I had convinced myself that these things were necessary in order to manage the complexity associated with the database model and to have a graphical model to use as a tool for communicating with business users.  The reality, though, was that I never showed a data model to a non-technical stakeholder and that the models I was working with were usually only a few dozen tables.  In fact though, I was really just trying to get around writing and maintaining SQL build scripts for each phase of development.  I don’t need to do that anymore because migrations builds the database out in parallel with the application.  Any complexity that was there before is now gone because I only need to focus on the parts of the database that I’m actively developing.
  3. It promotes iterative development of application features.  Under the old-school, database-first methodology, you would first build out a new iteration of your database model in order to support a new batch of features, fix all the code that you may have just broken, and then extend the application- and presentation-layer code as needed in order to get the desired behavior.  It seemed reasonable at the time, but in practice, the volumes of changes that were being pushed through during a given development cycle made this method a lot closer to the much-derided waterfall model of development than it was to anything that could be called iterative.  The flexibility afforded by Rails migrations is that it’s just as easy to do small changes to the database as it is to do large ones, and all the database versioning is done for you automatically.  So there’s no additional cost associated with making a lot of small changes to the database instead of batching them into one confused mess.
  4. Data can be loaded directly from your migrations.  There are a lot of different methods in use out there for inserting data directly into newly created database tables from a migration.  (You can find a good summary of the various possibilities with examples here.)  I use this a lot when I want to load reference data into lookup tables or when I need to insert a few rows into a table in order to bootstrap the application.  (Ex: if I need to have an Administrator account automatically inserted into a Users table in order to provide initial access to a protected application.)  If I’m only dealing with a few objects (3 or fewer) I’ll usually just create new model objects directly in the migration.  For larger collections, I’ll fall back on fixtures or data pulled from delimited text files with a few lines of custom code thrown into the mix to process the files.  Doing it this way allows business users to produce and edit the data files directly in Excel which requires very little in the way of explanation.
  5. There are a lot of great plugins available to fill gaps in migrations.  You can probably guess what my initial complaint was about migrations: no foreign key support.  The good news, like most other things within Rails, is that if there’s something that’s bothering you, there’s a good chance that it’s already been bothering someone else, perhaps even enough to write a plugin to solve it.  In this case, you can install redhillonrails_core or sexy_migrations to provide foreign key support along with a number of other nice-to-have features.  The lack of FK support wasn’t a deal breaker for me, but it was nice to know that I could apply a simple fix and get the behavior I wanted.
  6. Rake provides tasks that make it easy to manage your migrations.  I tend to use db:migrate:reset a lot during development to simply drop and recreate the database in its latest form.  In production environments, I usually use the more judicious db:migrate.  In both cases though, I find the whole process a helluva lot easier than when I was typing the database username and password, schema, and SQL script location into the command line.

Rails goes a long way toward closing the loop between application and database thanks to the object-relational integration provided by default in ActiveRecord.  Migrations really finishes the job by removing the separation between the two layers from the development process.  I started out looking at it just for the sake of completeness, and in the end, it really led to a massive improvement in my development process.