The Joy of Migrations
Posted by kev Thu, 27 Oct 2005 09:42:00 GMT
Migrations are one of the more exciting features in Rails today. I think they’re wonderful and that they should be used. I’ll go over how any why you should use these gems.
First the why…
- Migrations are database agnostic. What this means is that you can write your description of the database in ruby -once- and have it easily translate to MySQL, PostgreSQL or SQLite out of the box.
- Migrations mean never having to say “I don’t know the create syntax in
- Migrations allow for different versions of a database. This means that when you decide to add user authentication on that application that you know is horribly insecure, but “it would interfere with existing infrastructure”, you can do so easily and without complications.
- Because migrations allow for easy changes to database schemas it allows for good iterative development.
In general, migrations make developing a database driven application much much easier.
Now, this is going to blow your mind but you should be able to pick up migrations quickly. Here’s the quick version, then I’ll go more in depth.
For the impatient
- Step 1: Create a migration with
script/generate migration WhatImChanging
- Step 2: Modify your generated migration file in db/migrate
- Step 3: Run
- Step 4: Revel in the fact that your database is converted to the newest schema!
For the slightly less impatient
Step 1: Create a migration
script/generate migration WhatImChanging
The generate migration syntax allows you to describe the migration in CamelCase or with_underscores, and will generate a file in
For example, if I’m adding a users table to my schema I might do the following:
script/generate migration add_user_table
This might then create a file:
Step 2: Modify your migration file
The next step is to make your migration do something. A bare bones migration looks like this:
class AddUserTable < ActiveRecord::Migration def self.up end def self.down end end
This new class,
AddUserTable, is what describes our migration. The two methods, up and down, describe what actions need to be taken to upgrade to this version of the schema and how to downgrade if that is needed.
For our sample application we want to create a new User model and its table. To do this, we simply use the create_table syntax. I’m going to write out the whole migration and then we can talk about what it all means.
class AddUserTable < ActiveRecord::Migration def self.up create_table :users do |t| t.column :first_name, :string t.column :last_name, :string t.column :birthday, :date end end def self.down drop_table :users end end
Lets go through this a bit at a time.
create_table :users do |t| describes the creation of a table called users, passing that table as the variable t to the block. Inside the block we tell the table that it has columns named
create_table and friends are called schema statements and you can find the full range of them documented quite well in the rails api.
column is an example of a table definition method which can be found in a slightly different part of the api.
If you look at
self.down, you’ll see where we describe how to reverse our changes: we simply drop our users table.
All of the migrations methods look similar and follow similar syntax. You do something to a table or method and describe its attributes.
Step 3: Run the migration
This is the part that is going to blow your mind. Go to the command line and run
rake migrate. You’ve just been upgraded to your latest schema! If you want to reverse your changes, you can do so with
rake migrate VERSION=MyVersionNumber.
How this all works
So, basically, rake has a task described which goes to the database and says, “Hey. How old are you?” The database, though slightly offended, decides to give up the information in the schema_info table’s verion column. Rake compares that version to how many migrations it has and if it detects new migrations, it runs them one at a time.
What about changing tables that already have data?
Migrations are data safe. This is a wonderful thing. For example, in a recent project I had written a shopping cart system. Another developer had named a column visibility to determine if the product should be shown or not. I knew that rails treated boolean columns like boolean methods in ruby, so I felt it was more natural to call the column visible. This way, I could write
if myproduct.visible? as opposed to
if myproduct.visibility == true or
if myproduct.visibility?. Anyway, to make a long story short, here’s the migration in action:
class RenameProductVisibilityToVisible < ActiveRecord::Migration def self.up rename_column :products, :visibility, :visible end def self.down rename_column :products, :visible, :visibility end end
The data was safe, and I had my asthetically pleasing code.
Tips and Tricks
- If you’ve already got a database established and you want to work with migrations, use
rake db_schema_dump. This will dump your current schema into
db/schema.rbas migration markup. You can then create your initial schema with step 1 and copy schema.rb (the stuff inside ActiveRecord::Schema.define()) into self.up. You may also need to create a schema_info table with one column called version of type integer.
Something terribly witty and conclusive
Go forth and make database agnostic abstractions of your schemas! It’s good for you, really.