learns_to use migrations

As I mentioned earlier, I was recently hired to do actual paid work as a Rails developer (as unbelievable as that may seem to those of you who’ve actually met me). I’m beginning to realize that, in addition to this stupefying fact in itself, I also won the employer lottery. My bosses are three guys. They’re nice, highly communicative, and — best of all — extraordinarily responsive to my input at every level from implementation style all the way up to application design and even business model planning. I can’t say too much about the specifics of the job (they actually had us sign an NDA, how 1999!), but I can say that the guys are highly experienced java programmers who have more experience with web development and startups than I probably ever will (check out Jonathan’s resume. . .and he’s just one third).

This project is their first Rails app so part of what they want is, naturally enough, for me to share whatever inside dope I may have on Rails’ workings. I decided that the best way to do this would be for me to write a series of learns_to posts here on the topics I decide to cover and then follow-up in private with specifics that relate to what we’re building. That way, some poor Googling sap potentially gets some help on his questions (I was that poor sap not so long ago, so I know how much help the well-written random blog post can be) and the guys some customized documentation to get themselves, and anyone else they bring on in the future, up to speed double quick. So, without further ado, let’s learn Migrations!

With Migrations, Rails provides a way for you to abstract your database schema in code. In simple terms, a migration is a chunk of Ruby that represents a set of changes that you want to make to the structure of your database — which sounds complicated and confusing, but becomes suddenly clear if we look at an example. Say, we’re creating a simple blog application. We’ve got users and posts each of which have a couple of attributes. Here’s the migration we’d write to get our database set up.

class OurFirstMigration < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :name, :string
t.column :updated_at, :datetime
t.column :created_at, :datetime
end
create_table :posts do |t|
t.column :subject, :string
t.column :body, :text
t.column :updated_at, :datetime
t.column :created_at, :datetime
end
end
def self.down
drop_table :users
drop_table :posts
end
end

Now, this is pretty human readable. Like every migration, ours has two methods, self.up and self.down, the first of which gets run when we’re moving up through this particular migration making these changes for the first time, the second when we’re going the other way and undoing them (more on the rough-and-tumble details of actually running migrations a little later). The first is obvious: in self.up we create two tables, called “users” and “posts” respectively, each of which has a handful of columns that store various predictable attributes of users and posts. The second method, self.down, rolls back our changes, destroying the two tables we just created. Now, that doesn’t seem especially useful when we’re just at the point of creating our database in the first place. On the other hand if we were in our seventh or eighth iteration and had made radical (and potentially boneheaded) changes to our schema this method would come in incredibly handy. If we decided that we needed to undo our recent changes (to revert to an earlier database structure), we could do so without nuking the rest of our database schema or any of our data. You can think of these methods as built-in version control for your database.

So, you say: What exactly are the practical advantages of using migrations to manage changes to my database? I already keep my SQL schema under version control. That’s how they do it in the Agile Book. What do you know that DHH and Dave Thomas don’t? Well, the reason DHH and DT didn’t mention migrations in Agile is that, despite their other many achievements, neither of them have (yet) invented a time machine. Migrations are maybe the most exciting of a spate of great additions to Rails that first appeared around in the run up to version 1.0[*]. And their advantage is that they bring all the Rails virtues to database management, making it easy to learn, powerful to use, and beautiful to behold.

First of all, with migrations you’ll manage your database schema in Ruby just like the rest of your project; you don’t have to jump into SQL (or learn it in the first place) every time you want to make a change. Beyond just the convenience for you, staying within Ruby also means that Rails is able to offer the same two great virtues when you’re managing your database that it does everywhere else: abstraction and convenience. Migrations don’t care what kind of database you’re using. Rails will translate your migration into the right kind of instructions for any of the supported database formats (MySQL, PostgreSQL, SQLite, SQL Server, and Oracle). That means you can develop on MySQL or SQLite (or develop on one and collaborate with someone using the other) and then deploy on Postgres, all without a second thought. Abstracting out the specifics of talking to the database will mean fewer configuration headaches and, therefore, more flexibility to change your system around. And we’ve already seen the benefit of having access to Rails convenience methods in the concision and clarity of our sample migration. Methods like create_table, drop_table, add_column, and remove_column (see the Active Record Migration documentation for a complete list) make managing our schema as simple and painless as it could reasonably be.

Another almost unreasonably cool side effect of migrations being first class Ruby citizens is that you can use them to populate your tables as you’re creating them. Let’s say that we wanted to organize the users of our blog application into groups (i.e. Group would have_many :users and User would belong_to :group). We could write a migration that simultaneously creates the groups table, adds the foreign key to the users table, and fills in the necessary data to assign all of our existing users to a group like so:

def self.up
create_table :groups do |t|
t.column :name
end
add_column :users, :group_id, :integer
User.find(:all).each do |p|
p.group_id = 1
end
end

Rails is also smart enough to add an id to each table without having to be told (you actually have to tell it not to by saying something like “created_table :group, id => false do |t|. . .” if you want it not to create an id column, as you would if you were creating a join table).

So far so good. Let’s talk implementation. Let’s say you’ve just started creating your blog software. You’ve run the Rails command to create your project, created your production, development, and test databases, and edited database.yml so that Rails knows how to talk to them. Now it’s time time to write your migration. First, we’ve got to create the file. Rails, of course, gives us a command for this:

> script/generate migration OurFirstMigration

You can give your migration any camel-case name you’d like, Rails doesn’t care. In our example, this command would create a file called 001_our_first_migration.rb in /db/migrate. Open it up and you’ll find a blank migration with empty self.up and self.down methods. Populate them with your migration methods in the style of our first code example above. Once you’ve done that, all that’s left is to run the migration:

> rake migrate

That comand will bring you up through each of your exiting migrations in turn in order to jibe with the most advanced state of our schema. We’ve only written one migration so far, but look at your database. It’s got tables and columns, created just like we specified, oh my! But wait, that’s not all, rake migrate is also smart enough to take a version number. If you want to, say, migrate from the fifteenth iteration of your database schema back down to the seventh, all you’ve got to do is:

> rake migrate VERSION=7

Clearly, depending on the changes your migrations make to the schema some of your data can get lost in the shuffle. Rails isn’t actually magic. If you drop your posts table, the data in it gets dropped, too. So, think carefully when you design the granularity of your migrations. If you lump a bunch of schema changes together then it’s going to hurt all the more to roll them back when you’ve got real data. Conversely, while you’re developing feel free to migrate up and down willy nilly anytime you’ve accumulated too much nonsense data in your tables.

There’s probably plenty more to be said about migrations, but this should be enough to get you started. And, of course there’s always more reading you can do. For example, there’s a pretty good discussion on the Rails wiki under the heading Understanding Migrations and when in doubt you can always RTFM. So, go on. What are you waiting for? Rake migrate!

[*]My copy of the Agile book is dated August 2005, and while the Loud Thinking post announcing migrations went up on July 6th, I’m sure that was after the book had gone to press. Stupid dead tree-based publishing!

Tagged: , , ,

This entry was posted in learns_to. Bookmark the permalink.

0 Responses to learns_to use migrations

  1. What is “postgre”? It’s pronounced “Post gres cue ell”, so to get the short form, drop the last two syllables. I boggle at people that would abbreviate it as you did.

  2. Greg says:

    Thanks for pointing out the typo. But, dude, no reason to get all “boggled” ’cause a typo’s all it was.

  3. Miles says:

    haha boggle…
    Thanks for the article. Easy to read – I actually understand it, and I didn’t have any bouts with ADD causing me to surf elsewhere.
    Didn’t want you to be boggled by the lack of compliments. Or worse yet, discouraged that people who read your blog are the same who boggle at incorrect abbrevations. gdl … err, good luck.

  4. Greg says:

    Thanks, Miles, that’s great to hear! Any little step that these tutorials take towards making things easier for someone to understand is a success in my book. I started writing them because of how much time I myself spend googling around for some kind of plain English explanation whenever I need help with something technical. The least I can do is make it easier for the next guy. . .glad it worked!

  5. Efrén says:

    We noticed that your blog have some Ruby On Rails related content and that
    is why we would like to invite you to register yourself and your
    blog at RubyCorner, a directory of Ruby related blogs:
    http://www.rubycorner.com/
    We like to think about RubyCorner as a “meeting place” for the
    community, also as a “focal point” where the people new to this
    technology can quickly tune into the pulse of the community.
    Registering your blog will help build a valuable resource for
    this growing community.
    Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *