Leo's Technical Blog

Rails Migrations Gotcha: Backward-Incompatible Model Changes

Introduction

user

Leo Soto


ruby, rails, asking

Rails Migrations Gotcha: Backward-Incompatible Model Changes

Posted by Leo Soto on .
Featured

ruby, rails, asking

Rails Migrations Gotcha: Backward-Incompatible Model Changes

Posted by Leo Soto on .

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#dosomethingwith_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