Resetting ActiveRecord object columns in migrations

Posted by # October 3rd 03:48 PM

Often when you use migrations to keep track of your database schema, you also need to migrate data around in the database. You can use the execute method to do this in pure SQL, but since migrations are Ruby code and have access to all the ActiveRecord code, why not do it in there? Here’s an example from our latest project where I reverse a has_one—belongs_to association:

  def self.up
    add_column :images, :content_id, :integer

    Content.find(:all, :conditions => "image_id is not null").each do |entry|
      image = Image.find_by_id(entry.image_id) rescue nil
      if image
        image.content_id = entry.id
        image.save
      end
    end

    remove_column :contents, :image_id
  end

However, this has one problem. The Image class is loaded and the column information cached before the column content_id is added, so it doesn’t have the getter and setter methods for that column and thus image.content_id = entry.id will bomb.

Fortunately this is easy enough to fix. Just call Classname.reset_column_information after the column is added, and the new column will be found:

  def self.up
    add_column :images, :content_id, :integer

    Image.reset_column_information
    Content.find(:all, :conditions => "image_id is not null").each do |entry|
      image = Image.find_by_id(entry.image_id) rescue nil
      if image
        image.content_id = entry.id
        image.save
      end
    end

    remove_column :contents, :image_id
  end

rails Jump to comment form

Comments

  1. Daniel Von Fange 10.04.06 / 06AM

    It’s usualy better to use SQL because down the road you may change the image classs in such a way that it no longer will work with the old data. (If you add a vaildation in the future on a field that isn’t even created in this migration, for example)

  2. Jarkko 10.05.06 / 23PM

    Daniel,

    You’re right, I just noticed the same in some other context. So the example is not a very good one.

    However, there are cases where you need to work on the class level and in those cases reset_column_information is very useful.

    It’s just not very well documented, so I wanted to give some time in the limelights for it :-)

  3. Peter Marklund 10.21.06 / 12PM

    I ran into this gotcha too and was going to blog about it but you beat me to it. Note that you can redefine your ActiveRecord classes to protect against class changes, see http://rails.techno-weenie.net/tip/2006/2/23/safely_using_models_in_migrations

Recently

RailsConf 2007 Speaker

Beginning Ruby on Rails E-Commerce