Update 8/7/2014: This is now available on github and rubygems.org as the branchy gem – github.com/consolo/branchy.

Rails’ database schema and migration management is still one of the things that sets it apart from other frameworks. That said, if you don’t use the tools exactly as intended, you’ll be pounding your fist on your desk and stabbing yourself in the eye. Repeatedly.

Below I’ve compiled some notes about Rails migration etiquette and best-practices that I’ve discovered or been taught over the years. They are particularly relevant when working on large-ish teams and codebases, but even hobby projects can benefit.

Use db/schema.rb

I’ve seen a many Rails projects screw this up, including some you’ve probably heard of. When setting up any new environment be it local development or production, create your database from db/schema.rb by running “rake db:schema:load”. (Or use “rake db:setup” which does that plus db/seeds.rb.) Do not setup your database by just running all the migrations. Migrations are for transforming the database from one point in history (commit) to another. schema.rb represents the HEAD of your database history, if you will.

Commit db/schema.rb

In case it’s not now obvious, schema.rb should be committed to your repository. Whenever you commit a migration, commit the changes in schema.rb as well. This cannot be stressed enough. A commit’s code “state” and schema “state” should always match. Then, when someone checks out your branch, they and “rake db:setup” know exactly what the schema should look like.

Before comitting a change in schema.rb it’s a good idea to diff it. The only diff you should see is the change made by your migration(s). If anything else shows up then someone (possibly you) has screwed up. You can usually fix it by running “git checkout db/schema.rb && rake db:reset && rake db:migrate”. Then check the diff again to be sure.

Use branched databases

When I was first introduced to branched databases, rainbows appeared, unicorns flew through the air, all those songs on the radio suddenly made sense, and Clarence Odbody from It’s a Wonderful Life finally got his wings.

In your development and testing environments each git branch should have its own isolated database. This keeps database changes in branch A from breaking branch B’s code. It also helps you keep your db/schema.rb in pristine condition. Here’s a simple example with git:

<% branch = `git symbolic-ref HEAD 2>/dev/null`.chomp.sub('refs/heads/', '') %>

development:
  ...
  database: myapp_development_<%= branch %>

test:
  ...
  database: myapp_test_<%= branch %>
  ...

Merging db/schema.rb

If you’re following the suggestions above, merging your branch’s schema.rb changes into mainline should be very clean. Usually the only conflict will be the schema version line. In those cases, the highest version always wins.

If there are massive conflicts then it’s likely someone on your team (possibly you) has screwed up, and that mainline’s schema.rb does not reflect the true current state. Fixing that isn’t pretty, but it is straightforward. In your production environment run “RAILS_ENV=production rake db:schema:dump”. That will write production’s current schema to db/schema.rb. Commit it and merge it into all other branches. Then each developer on your team needs to drop all of their databases and start from scratch. After a few rounds of that your team will hopefully work out the bugs in their process.