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

On Jan 7, 2008, at 1:37 PM, Josh wrote:

add_column :users, :first_name, :string
add_column :users, :last_name, :string

  User.reset_column_information

end
Thanks.
Rob B. http://agileconsultingllc.com
[email protected]

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:

On Jan 7, 2008 2:14 PM, Rob B. [email protected]
wrote:

will do the same think (and a lot faster, too!)
I’m not sure that this is what’s going on though.

On Jan 7, 12:30 pm, Josh [email protected] wrote:

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.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/