Wednesday, April 9, 2008

Rails Migrations Gotcha: Backward-Incompatible Model Changes

I'm pushing for adoption of Rails Migration on all Rails projects on my job (we use them on a few). As a consequence, I won the assignment of writing migrations for the last changes on the system I'm currently involved. That seemed easy, but it wasn't. I will try to show why, without diving into details of my specific scenario.

Imagine you have the following model:

class Foo < ActiveRecord::Base
end

And the following migration:

class AddAnotherFieldToFoo < ActiveRecord::Migration
def self.up
add_column :foo, :new_column, :string
Foo.reset_column_information
Foo.find(:all).each do |foo|
foo.new_column = some_calculation(foo.another_column)
foo.save!
end
end
end

Now, we make the following changes to our model:

class Foo < ActiveRecord::Base
has_many :bars
before_save :do_something_with_my_bars
def do_something_with_my_bars
...
end
end

And its migration (just for completeness, not really relevant):

class AddBazToFoo < ActiveRecord::Migration
def self.up
add_column :foo, :bar_id, :integer
end
end

So what is the problem?

For us, who made the last change on Foo after doing the AddAnotherFieldToFoo migration, it's all fine.

But, for the new developer who just made a checkout of the source code and happily executed rake db:migrate, the AddAnotherFieldToFoo migration failed miserably.

That's because Foo#do_something_with_bars will get called (remember the :before_save we introduced), but the association between foo and bar is not made yet (we are executing a previous migration).

Same happens to the developer who didn't update his local copy this week. And it will break on production too, when we merge this set of changes into the production branch.

So, here is my problem:

Every backward incompatible change to models will (potentially) break past migrations, because they are not specifically associated to a model state on the time.

And SCMs doesn't help either (updating one changeset at time would work, but when merging braches all that changesets will collapse into one and you are doomed) I'm looking into what to do. Maybe I'm using migrations in a way they were not intended to be used...

Does someone know how to solve this?

Update: Here is the ruby-talk thread

0 comments: