Migrations - they're lovely

We're not talking about swallows (African, European or otherwise) but Database Migrations that ship with Ruby on Rails.

There's a cool bit of functionality which lets you stores database schema and incrimental changes in code files as well as a framework which offers the ability to roll the database forwards or backwards through versions.

The code is really simple, here's some code which creates a Users table: -

class AddUsersTable < ActiveRecord::Migration
  def self.up
    create_table :users, :id => false do |table|
      table.column :UserId, :integer
      table.column :Username, :string
      table.column :Password, :string
      table.column :EmailAddress, :string
    end
  end
  def self.down
    drop_table :users
  end
end

So, what does this do? Well this code is stored in a file inside my migrations folder called "001_add_users_table.rb". Take note of the "001" that indicates that this file is the first inside my Migration, it's the first step to creating my schema.

Notice there's a self.down towards the bottom of the file, this is executed when the database schema is rolled backwards to version 0 (remember that file is version 1 so version 0 is the database before version 1 ran, thus we need to drop the table).

I can tell the framework to change my database to a particular version by executing the following command line: -

rake migration VERSION=1

The framework stores the current database "version number" inside the database in a special single column single row table.

So, when I run that command line the following SQL get's executed against my SQL Server.

set implicit_transactions on 
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, 
	DATA_TYPE as ColType, IS_NULLABLE As IsNullable, 
	COL_LENGTH('schema_info', COLUMN_NAME) as Length, 
	COLUMNPROPERTY(OBJECT_ID('schema_info'), COLUMN_NAME, 'IsIdentity') as IsIdentity, 
	NUMERIC_SCALE as Scale 
FROM INFORMATION_SCHEMA.COLUMNS 
WHERE TABLE_NAME = 'schema_info'
go
SELECT TOP 1 version FROM schema_info
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
SELECT TOP 1 version FROM schema_info
go
CREATE TABLE users ([UserId] int, 
			[Username] varchar(255), 
			[Password] varchar(255), 
			[EmailAddress] varchar(255)) 
go
IF @@TRANCOUNT > 0 COMMIT TRAN
go
UPDATE schema_info SET version = 1
go
IF @@TRANCOUNT > 0 COMMIT TRAN
go
SELECT @@ROWCOUNT AS AffectedRows
go
SELECT TOP 1 * FROM schema_info
go
SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'
go
SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, 
	DATA_TYPE as ColType, IS_NULLABLE As IsNullable, 
	COL_LENGTH('users', COLUMN_NAME) as Length, 
	COLUMNPROPERTY(OBJECT_ID('users'), COLUMN_NAME, 'IsIdentity') as IsIdentity, 
	NUMERIC_SCALE as Scale 
FROM INFORMATION_SCHEMA.COLUMNS 
WHERE TABLE_NAME = 'users'
go
EXEC sp_helpindex users
go
IF @@TRANCOUNT > 0 COMMIT TRAN
go

Now, when I run the reverse command and roll the system back I run this: -

rake migration VERSION=0

Which in turn pumps the following SQL into the database: -

set implicit_transactions on 
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, 
	DATA_TYPE as ColType, IS_NULLABLE As IsNullable, 
	COL_LENGTH('schema_info', COLUMN_NAME) as Length, 
	COLUMNPROPERTY(OBJECT_ID('schema_info'), COLUMN_NAME, 'IsIdentity') as IsIdentity, 
	NUMERIC_SCALE as Scale 
FROM INFORMATION_SCHEMA.COLUMNS 
WHERE TABLE_NAME = 'schema_info'
go
SELECT TOP 1 version FROM schema_info
go
SELECT TOP 1 version FROM schema_info
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
CREATE TABLE schema_info (version int)
go
select * from CREATE TABLE schema_info (version int)
go
SELECT TOP 1 version FROM schema_info
go
SELECT TOP 1 version FROM schema_info
go
SELECT TOP 1 version FROM schema_info
go
SELECT TOP 1 version FROM schema_info
go
SELECT TOP 1 version FROM schema_info
go
DROP TABLE users
go
IF @@TRANCOUNT > 0 COMMIT TRAN
go
UPDATE schema_info SET version = 0
go
IF @@TRANCOUNT > 0 COMMIT TRAN
go
SELECT @@ROWCOUNT AS AffectedRows
go
SELECT TOP 1 * FROM schema_info
go
SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'
go
IF @@TRANCOUNT > 0 ROLLBACK TRAN
go

Pretty cool isn't it? :-)

Well - why does this matter? Just think source control and you'll have the same.

Published Thursday, October 12, 2006 9:22 PM by Plip

Comments

# re: Migrations - they're lovely

Thursday, October 12, 2006 11:40 PM by James

What if there's data in the tables?

# re: Migrations - they're lovely

Friday, October 13, 2006 11:56 AM by Scott

James: my experience is the data is saved. If you are changing a data type in the table the normal errors apply if you try converting to an incompatible type. admittedly, I've only worked with ActiveRecord migratins with MySQL. Using SQL Server the experience may vary.

I too love migrations, Django has something similar. I'm always amazed at how many Rails clones I see my ASP.NET, JSF, etc... But no one ever tries to clone migrations. Between that and the built in unit testing, I can deal with all the aligators in my HTML templates. Even if it does remind me of ASP development(shudder)

# re: Migrations - they're lovely

Friday, October 13, 2006 1:21 PM by Graham Pengelly

I've been noodling with the .Net attempts at implementing some of the Rails ideas. Have a look at Subsonic on Codeplex (http://www.codeplex.com/Wiki/View.aspx?ProjectName=actionpack) It has got a simple but apparently effective DB migration tool that emits a sql file for the schema and one for the data that can then go into source control.

# re: Migrations - they're lovely

Friday, October 13, 2006 4:52 PM by Shane

For .NET people considering Rails, this might be an interesting read:

http://www.infoq.com/articles/Netter-on-Rails

Have fun :)

# re: Migrations - they're lovely

Wednesday, November 01, 2006 11:45 PM by Andrew

Why did you write this? Seriously there are already a million intro to Rails Migrations pages on the internet that say exactly the same thing. This page is just noise.

# re: Migrations - they're lovely

Monday, November 13, 2006 4:56 AM by Daniel

Andrew >>

Why did you write that comment? Seriously there are already a million comments on the internet that say exactly the same thing. That comment is just noise.

Leave a Comment

(required) 
(required) 
(optional)
(required)