Rants About Rails Database Migrations
Migrations as version control for databases
When I first learned about Rails migration, I immediately thought that dropping down a revision would be something you would do often. In actuality, Rails migrations is more like a version control for databases. Since databases changes require modifications to both code and data, you cannot simply use a source code version control system like Subversion or Sourcesafe. You need a more advanced revisioning system for databases… hence Rails Migrations.
Because of my inexperience with databases, I mistakenly thought that the purpose of migrations was to allow the ability to go up and down migrations at will.. But the purpose is more to restore an older version of the database like you would restore an older version of source code with Subversion. I would think that when you migrate down, all of your business logic would probably migrate down at the same time. What are thoughts on this? Any comments?
In a nutshell, Rails migrations are really just a database agnostic way of running SQL scripts in an organized, standardized way. Migrations can obviously be used outside of web development.
Relationship between model and migration
In Rails, a model internally maps itself to a database table. The table in the database must be the plural form of the model’s class. If we generate a model called
User, Rails automatically looks for a table called
users in the database.
You can use the Rails generator to generate both the model and a corresponding migration as shown here (You can also generate a migration by itself – for example,
add_column to an existing model)
ruby script/generate model User #run Rails model
The model and migration are both generated:
db/migrate/001_create_users.rb #migration that creates database table
And the generator also generates some test stuff:
test/unit/user_test.rb #unit test for the model
test/fixtures/users.yml #sample data for the model
The migration is generated so that it will use the plural form of the model for the database table name. The important thing to note is that after the migration is generated, the class name of the model (
User) and the class name for the migration (
CreateUsers) are not linked in anyway. The only link is at runtime and that is only between the model and the database table name. For example, when you do a query with the
User model (
User.find), then ActiveRecord will automatically query on the
users table in the MySQL database (or whichever database you’re using). The migration is only used to set up the database and this is never done during runtime.
app/models/user.rb)class User < ActiveRecord::Base
db/migrate/001_create_users.rb)class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.column :user_id, :int end end def self.down drop_table :users end end
The cool part about ActiveRecord’s ORM is that the model never needs to maintain the column names. It learns them during runtime, as seen by this empty
Migrations on a per table basis
One drawback of Rails migrations is that all migrations occur at the database level, not the table level. If you want to restore the database for only one table, you’ll have to do something special, which I haven’t figured out just yet.
Renaming the model when the model is referenced inside the migration
If you rename a model and the model is referenced inside a migration, it might break your migrations. When would you ever reference the model inside the migration? When you want to make changes to the actual data in the database, it’s easier to code with the model than raw SQL. In this case, you can actually define the model in the migration itself to prevent it from breaking if you rename the model. This is described in Rails Recipes by Chad Fowler
Advanced features and options of Rails migrations:
- Mike Perham’s writeup of the usage
:limit, :null, add_index,and foreign key support
- Some advanced migration features (
:force, :temporary, :opitons, executenative SQL)