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: