I recently hit upon the Enhanced Migrations plugin by Revolution on Rails. Works great when you develop on branches. The #dump_schema_information method of ActiveRecord::ConnectionAdapters::SchemaStatements only dumps the most recently migration file. Since each migration is now a separate entry in the migrations_info table, we can’t report only the latest one.

To this end, I generated the following diff:


1 $ svn diff vendor
2 Index: vendor/plugins/enhanced_migrations/lib/enhanced_migrations.rb
3 ===============
4 - vendor/plugins/enhanced_migrations/lib/enhanced_migrations.rb (revision 7767)
5 + vendor/plugins/enhanced_migrations/lib/enhanced_migrations.rb (working copy)
6 </span><span class="er"> -58,8 +58,8 </span><span class="er">
7
8 ActiveRecord::ConnectionAdapters::SchemaStatements.send(:define_method, :dump_schema_information) do
9 begin
10if (current_schema = ActiveRecord::Migrator.current_version) > 0
11return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} VALUES (#{current_schema}, NOW"
12 + select_all("SELECT * FROM #{ActiveRecord::Migrator.schema_info_table_name} ORDER BY created_at, id").map do |migration|
13 + "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} VALUES;\n"
14 end
15 rescue ActiveRecord::StatementInvalid
16 # No Schema Info

Hope this is useful for other people.

As I have reported on the rubyonrails-core mailing list, I have a working solution. Right now, this is a patch against Rails Edge. I am trying to move this code into a plugin, but I have problems with dependencies. I override methods in Rails::Generator::Base, but there is no file named rails/generator/base.rb. That constant is defined in rails_generator/commands.rb, which makes it very hard for the ActiveSupport dependency mechanism to load it.

In the hopes that someone could show me the way, I am making the patch available through the Rails Trac on issue #6838. The patch is timestamped_migrations.v1.patch

What this patch does:

  • Generates migrations where the version number is replaced with a UTC timestamp;
  • Allows migrating up and down, ordering migrations by name;
  • Records which migrations were run in the schema_migrations table.

This patch has no tests, but from the command line it seems to work. It even works with the db:sessions:create Rake task.

Enjoy, and either leave comments here or at francois.beausoleil@gmail.com.

2006-12-13 Update: Opened a new ticket as #6838, from #6799.

courtenay just sent in a new ticket for Rails Core: #6799. To summarize, his patch allows two migrations to have the same number, and migrates them in parallel. courtenay blogged about his patch as simultaneous migrations

I’ve written a patch to migration code so you can have multiple migrations with the same number. This means that these migrations can effectively be run in parallel, so long as all the migrations with that number have the IndependentMigration class.

His patch is interesting, and solves a real problem. But, his two caveats render the solution slightly too complex.

What if instead of version numbers we had migrations with timestamps ? We would need a few changes for that to be useful:

  • All timestamps must be in the UTC timezone;
  • The schema_info table must change (or be replaced) to have many records instead of only one.

Then, when we run the migrations, we order the migration files by name, and check if the record exists in the schema_info table. If it doesn’t, we migrate and record the time at which the migration ran.

Example

Let’s assume we are on trunk/. The following migrations already exists.

db/
  migrate/
    20061208111503_create_posts.rb
    20061208111504_create_tags.rb
    20061208111505_create_taggings.rb

The migrations table I was talking about above would look something like this:


1 create_table :schema_migrations, :force => true do |t|
2 t.column :migration, :string, :limit => 20, :null => false
3 t.column :migrated_at, :datetime
4 end
5
6 add_index :schema_migrations, :migration

We have another developer who was working on another branch, and he just merged back to trunk/. Let’s update:


1 $ svn update
2 A db/migrate/20061208161723_create_links.rb
3 A db/migrate/20061208111303_create_users.rb

Now that we have his code, let’s migrate and run the tests


1 $ rake db:migrate test:recent
2 == CreateUsers: migrating
3
4 == CreateLinks: migrating
5
6
7
8
9 87 tests, 125 assertions, 0 failures, 0 errors

CreateUsers was run because it’s timestamp comes before CreatePosts. CreatePosts, CreateTags and CreateTaggings weren’t run because they had already been run. Finally, CreateLinks was run because it hadn’t been run yet.

The only thing missing now is the interim world. What do we do with existing migrations ? I believe we should simply migrate all low-level migrations before we do the new ones. That should ensure all migrations are up to date, no ?

I know code speaks louder than words. Just give me a couple of hours, will you ?

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on