migrations ignoring record changes

I had a users table and went back to add columns for first name and last name. Just so it would not cause errors, I was trying to loop through the existing users in the database and set the first name column to "First" and the last name column to "Last"

Before updating to rails 2, i was able to do something like this:

class AddFirstNameAndLastNameToUsers < ActiveRecord::Migration   def self.up     add_column :users, :first_name, :string     add_column :users, :last_name, :string

    User.find(:all).each do |user|       user.first_name = 'First'       user.last_name = 'Last'       user.save     end   end

  def self.down     remove_column :users, :first_name     remove_column :users, :last_name   end end

For some reason, the columns are created, but the names are never changed. When looking at the database, everything is still NULL. When calling save on the user object, I see MYSQL updates being made, but without the columns that were just added.

I can't figure out why this won't work and would greatly appreciate any insight.

Thanks.

Almost immediately after posting this, I tried splitting this into two separate migrations. One to add/remove the columns and the one after that to update all of the records in the database. Running rake db:migrate and updating everything at once didn't work, but running the migration all the way up to the version that adds the columns, and then re-running rake db:migrate a second time worked. So I guess the model is just not being reloaded once the new columns are added.

Is it possible that this is a bug, or is this the way it was meant to work?

In general, you have to tell ActiveRecord that it needs to rethink the columns that the database has:

  User.reset_column_information

But in this case, there's not really a need to get the model involved.

  User.update_all(['first_name = ?, last_name = ?', "First", "Last"])

will do the same think (and a lot faster, too!)

-Rob

Almost immediately after posting this, I tried splitting this into two separate migrations. One to add/remove the columns and the one after that to update all of the records in the database. Running rake db:migrate and updating everything at once didn't work, but running the migration all the way up to the version that adds the columns, and then re-running rake db:migrate a second time worked. So I guess the model is just not being reloaded once the new columns are added.

Is it possible that this is a bug, or is this the way it was meant to work?

I had a users table and went back to add columns for first name and last name. Just so it would not cause errors, I was trying to loop through the existing users in the database and set the first name column to "First" and the last name column to "Last"

Before updating to rails 2, i was able to do something like this:

class AddFirstNameAndLastNameToUsers < ActiveRecord::Migration def self.up    add_column :users, :first_name, :string    add_column :users, :last_name, :string

      User.reset_column_information

   User.find(:all).each do |user|      user.first_name = 'First'      user.last_name = 'Last'      user.save    end end

def self.down    remove_column :users, :first_name    remove_column :users, :last_name end end

For some reason, the columns are created, but the names are never changed. When looking at the database, everything is still NULL. When calling save on the user object, I see MYSQL updates being made, but without the columns that were just added.

I can't figure out why this won't work and would greatly appreciate any insight.

Thanks.

Rob Biedenharn http://agileconsultingllc.com Rob@AgileConsultingLLC.com

In general, you have to tell ActiveRecord that it needs to rethink the columns that the database has:

        User.reset_column_information

But in this case, there's not really a need to get the model involved.

        User.update_all(['first_name = ?, last_name = ?', "First", "Last"])

will do the same think (and a lot faster, too!)

I'm not sure that this is what's going on though.

>> Before updating to rails 2, i was able to do something like this: >> >> class AddFirstNameAndLastNameToUsers < ActiveRecord::Migration >> def self.up >> add_column :users, :first_name, :string >> add_column :users, :last_name, :string >> User.find(:all).each do |user| >> user.first_name = 'First'

If AR wasn't rereading the table definition, then that user.first_name should have raised a method_missing exception wouldn't it.

UNLESS, his model already had defined first_name=

On the other hand I notice that he's using save instead of save! so we don't know whether or not any validations are failing.

In any event, it should be pointed out that it's not a good idea to let migrations find AR classes this way, because migrations can fail or worse when the model class changes.

The safe way to do this is to do something like

class AddFirstNameAndLastNameToUsers < ActiveRecord::Migration

    class User < ActiveRecord::Base     end     ...

You can of course add relationship declarations and even methods if you need them to the user class (which will actually be AddFirstNameAndLastNameToUsers::User)

The point is that the behaviour of the model class is effectively frozen for this point in the migration stream.

I recently had a similar problem in our project. The migrations would pass when running one by one, but would fail when run all in sequence when bootstrapping the database. Finding the problem and fixing it led me to discovery that we had a lot more 'potential' problems in our large number of migrations. So I came to realize the following principle:

You should NOT use your models in migrations.

Think about it. This migration might need to run to bootstrap an empty database later down the road, when the model might not even exist. or its fields/methods will change. User model is usually more stable then the rest but still.

What you should do is to define your model AGAIN in your migration together with all the functions and other models it might use during the migration. In your case:

class AddFirstNameAndLastNameToUsers < ActiveRecord::Migration

  # you need to copy all the member functions that you use during the migration   # this way you don't have to worry later when you change/remove this modal that   # your migrations will fail. and you DON'T need new functionality in old migration. ever. well, except *may be* for fixing bugs :slight_smile:   # Also you MUST define it inside the migration class or unique module, so that it will be different from such models used in other migrations as all migrations are loaded together.   class User < ActiveRecord::Base   end

  def self.up     add_column :users, :first_name, :string     add_column :users, :last_name, :string

    # ALSO make sure you use your model for the first time AFTER you change its table schema     # or call reset_column_information as Rob suggested.

    User.find(:all).each do |user| # <<==== This is usually a bad thing to do. If you have BIG user table and use some eager loading, it will load ALL into memory       user.first_name = 'First'       user.last_name = 'Last'       user.save     end   end   def self.down     remove_column :users, :first_name     remove_column :users, :last_name   end end

And BTW, why don't you just User.update_all "first_name='First', last_name='Last'" ? :slight_smile: