Nov/07 3 4:52 pm

migrations

rails, technical

no comments


Ruby on Rails migrations allows the complete definition of an Object Relationship Mapping in just one place. Using a more traditional approach of Java and Hibernate would require DDL to make the database table changes, possibly SQL to populate default values, XML mapping files and finally a Java class complete with getter and setter methods.

Rails achieves all of this in just one migration file. Each migration contains a couple of methods - one to move up to this version of the database model, and one to move down. Typically these might respectively contain code to create a new table and drop it.

For example, to add a simple users table:

class CreateUsers < ActiveRecord:Migration
  def self.up
    create_table :users do |t|
      t.column :username,  :string, :limit => 12,
                           :null => false
      t.column :full_name, :string, :limit => 40, 
                           :null => false
    end
  end

  def self.down
    drop_table :users
  end
end

The migration file does the job of the DDL and SQL allowing both database structure changes and data setup to occur. Rails’ axiom of convention over configuration removes the need for any mapping file or even the definition of the underlying class.

To show what else is possible in a migration it’s possible to add a unique constraint:

def self.up
  ...
  add_index :users, :username, :unique => true
end

Or add an admin user:

def self.up
  ...
  User.create :username => 'admin', :full_name => 'Admin'
end

As the contents of the up method are simply executed in sequence it’s possible to get around chicken and egg type scenarios. Suppose for audit purposes our user table holds a couple of fields to indicate which user created a new user and updated an existing user. Normally these fields would not allow nulls because they can always be populated. But who created the default admin user? It makes sense to be admin itself however, how can we defined the creator as a user who doesn’t exist? The solution is simple. Create the table allowing the created by field to allow nulls, add the admin user, set the created by field to this new user then update the table to no longer allow nulls.

def self.up
  create_table :users do |t|
    ...
    t.column :created_by, :integer
  end

  User.create :username => 'admin', :full_name => 'Admin'

  admin_user = User.find_by_username('admin')
  admin_user.created_by = admin_user
  admin_user.save
	
  change_column :users, :created_by, :integer, 
                                     :null => false

leave a comment

previously

archives

categories

search