on Aug 28th, 2006Why Ruby on Rails is Awesome Part 1 - Migrations

I've been doing web development since 1994 and Ruby on Rails is a breath of fresh air. In the past I have developed sites in Perl, Php, Asp.net, and Java. Ruby on Rails blows them all away in terms of ease of use and development time. I thought I would go through the specifics of what I like about Rails. The first feature I will talk about is Migrations

What are they?

Migrations are the Rails way of versioning your database. If you've ever created and maintained a complex web application you probably know that database versioning is a very complex problem and there's just not a standard way to do it.

How do they work?

Migrations are Ruby classes that have an "up" and "down" method. The "up" method contains code that will upgrade your database to the version that this class represents. The "down" method contains code that will downgrade your database to the previous verison. The version is determined by the filename of the class. By default the names follow the pattern of xyz_name.rb where xyz is a number from 0 to 999. The version number is actually stored in a "schema_info" table that is automatically created when your first migration is executed. For example, here are the migration filenames from one of my Rails projects:

  • 001_create_restaurants.rb
  • 002_create_users.rb
  • 003_create_ratings.rb
  • 004_create_tags.rb
  • 005_create_taggings.rb

Here is the code from 001_create_restaurants.rb:

RUBY:
  1. class CreateRestaurants <ActiveRecord::Migration
  2.  
  3.   def self.up
  4.     create_table :restaurants do |t|
  5.       t.column :name, :string, :limit => 150, :null => false
  6.       t.column :country, :string, :limit => 150, :null => false
  7.       t.column :state, :string, :limit => 150
  8.       t.column :city, :string, :limit => 150, :null => false
  9.       t.column :postal_code, :string, :limit => 20
  10.       t.column :address, :string, :limit => 200
  11.       t.column :phone, :string, :limit => 50
  12.       t.column :hours, :string, :limit => 150
  13.       t.column :minimum_age, :integer
  14.       t.column :url, :string, :limit => 250
  15.       t.column :deliver, :boolean
  16.       t.column :sells_beer, :boolean
  17.       t.column :sells_wine, :boolean
  18.       t.column :sells_liquor, :boolean
  19.       t.column :cater, :boolean
  20.       t.column :payment_type, :string, :limit => 100
  21.       t.column :ranking_count, :integer, :default => 0, :null => false
  22.       t.column :calculated_rating, :float, :default => 0.0, :null => false
  23.       t.column :created_on, :datetime, :null => false
  24.       t.column :updated_on, :datetime, :null => false
  25.     end
  26.  
  27.   end
  28.  
  29.   def self.down
  30.     drop_table :restaurants
  31.   end
  32. end

To execute this migration, and create my restaurants table I just run rake migrate. This will create the table and set my database version at 1. To downgrade back to version 0, I can just run rake migrate VERSION=0.

You probably noticed that the above code is not normal SQL code. it's a Ruby specific DSL for modifying a SQL schema. This allows you to write agnostic SQL code that can be executed on a variety of SQL flavors(we will get to the benefits of this a bit later). The supported methods are:

  • create_table
  • drop_table
  • rename_table
  • add_column
  • rename_column
  • change_column
  • remove_column
  • add_index
  • remove_index

The full documentation on these methods can be found here.
You also have full access to your ActiveRecord objects for when you have to transform/modify data. As a last resort you can execute raw SQL with the execute method.

Is there an easy way to create these migrations?

You can create the migration classes by hand if you want to, but Rails of course has an easy way. You use the Migrations Generator with the syntax: ruby script/generate migration name_of_migration. In recent version of Rails Migrations are auto-generated when you generate any Model class, which is cool.

So what's so cool about this again?

If you use the Rails schema methods to create your tables, you will have an implementation agnostic representation of your database. This means you can easily switch your database type with no code changes. So I could change my app to use MySql instead of Postgresql if I wanted to by just changing my database.yml to point to a MySql database and running rake migrate!

Problems with Migrations

There's really only 1 problem and that's the fact that Migrations are not automatically executed within a transaction, which can lead to some problems if you have a bug in your migration code. There is nothing stopping you from writing your own transactions in your migrations, but that's going to make everything more complex. There is also a transactional_migrations plugin over at RedHill Consulting that will fix this problem.

Other Misc Tips and Tricks

  • If you are creating a new rails application from scratch and using an existing schema, you should create a "create_intial_database" migration with the existing schema definition. There's an easy rails way of generating a schema.rb with rake db_schema_dump.
  • There are a bunch of very helpful migration/schema related plugins available at RedHill Consulting like foreign key support, schema validations, and transactional migrations.

Trackback URI | Comments RSS

Leave a Reply