Stand-alone ActiveRecord migrations

Currently working on a merb project using datamapper - nice framework, but the auto-migrate feature is fairly limited - So I decided to hook up ActiveRecord’s migrations into the app. This code could be equally as easily adapted in to anything else.

First, I created a folder for the migrations to live in:

1
mkdir -p db/ar_migrations

I then appended a few tasks ripped out of Rails and slightly tweaked to the Rakefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
APP_BASE = File.dirname(File.expand_path(__FILE__))
 
namespace :db do
  task :ar_init do
    # Load the database config
    require 'active_record'
    database_yml = YAML::load(File.open(APP_BASE + "/config/database.yml"))
    init_env == "rake" ? current_env = "development".to_sym : current_env = init_env.to_sym # Default to dev env, symbolize
    ActiveRecord::Base.establish_connection(database_yml[current_env])
    # set a logger for STDOUT
    ActiveRecord::Base.logger = Logger.new(STDOUT)
  end
 
  desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x. Turn off output with VERBOSE=false."
  task :migrate => :ar_init  do
    ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
    ActiveRecord::Migrator.migrate(APP_BASE + "/db/ar_migrations/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
    Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
  end
 
  namespace :schema do
    desc "Create a db/ar_schema.rb file that can be portably used against any DB supported by AR"
    task :dump => :ar_init do
      require 'active_record/schema_dumper'
      File.open(ENV['SCHEMA'] || APP_BASE + "/db/ar_schema.rb", "w") do |file|
        ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
      end
    end
 
    desc "Load a ar_schema.rb file into the database"
    task :load => :ar_init do
      file = ENV['SCHEMA'] || APP_BASE + "/db/ar_schema.rb"
      load(file)
    end
  end
end

These will read from database.yml in config/ , so make sure you’ve got datamapper set up.

Lastly, Let’s create an example migration to create the datamapper sessions table in db/ar_migrations/001_create_dm_session_table.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CreateDmSessionTable < ActiveRecord::Migration
  def self.up
    create_table(:sessions, :id => false) do |t|
      t.string :session_id, :null => false
      t.text :data
      t.timestamp :updated_at
    end
    # Need this because ar in unhappy with a varchar primary key
    sql = "ALTER TABLE sessions ADD CONSTRAINT sessions_pkey PRIMARY KEY  ( session_id )"
    execute(sql)
 
    add_index :sessions, :session_id
    add_index :sessions, :updated_at
  end
 
  def self.down
    drop_table :sessions
  end
end

Once this is done, you can run rake db:migrate , and the DB will get the sessions table added. The db:schema:load task is also there, so you should use this when you first deploy your app to a new server. The naming is also consistent with rails, so capistrano should interact with it out of the box, with the exception that the environment to load/migrate needs to be called MERB_ENV instead of RAILS_ENV

When creating additional migrations you will need to follow the ‘proper’ naming scheme: the filename should start with an incrementing three digit number, and the rest should be a ‘flattened’ version of the class name. (i.e migration class AddPostTable becomes add_post_table).

1 Comment so far

  1. Matthew Williams on August 7th, 2008

    Have you elaborated on this at all lately? I’m looking for (or to build) a framework for working with ActiveRecord migrations outside of Rails for every day database work. I would love to have the versioning and generator scripts that Rails has.

    I haven’t looked to see what kind of task it would be to extract all of the appropriate tasks and code from Rails but it would be very helpful.

    Great post, thanks!

Leave a reply