Avoiding reflection errors in migrations

I’ve hit an issue a couple times now with migrations that I’ve solved
by hacking around, but now I’m interested in finding a more robust
solution.

The problem comes when a migration contains some logic that requires
the use of some class. However, within that class a new dependency has
been added that relies on a migration that comes after the current
one. Here’s a simple exmaple of what I mean:

— test_class.rb
class Test < ActiveRecord::Base
has_many :foo
def self.some_method
# do something useful
end
end

---- 002_update_test_class.rb
class UpdateTest < ActiveRecord::Migration
def self.up
add_column :tests, :my_column, :integer
Test.some_method
end
end

— 003_update_test_again.rb
class UpdateTestAgain < ActiveRecord::Migration
def self.up
create_table(:foos) do
# stuff
end
end
end

Running all migrations would result in the following error:
Mysql::Error: #42S02Table ‘my_table.foos’ doesn’t exist: SHOW FIELDS
FROM foos

So you see, the Test class is in an invalid state (it’s dependencies
don’t exist yet) when I’m calling it from UpdateTest migration, and it
will not be valid until the create_table happens in the next
migration.

Have other folks come across this? I’d be interested in hearing what
solutions others have employed. One thing I’ve been thinking is that
migrations could be separate out into two separate pieces: schema
changes and content changes. So you’d have something like “up_logic”
as well as the standard “up” and after running through the "up"s for
all migrations rake would then make a second pass running all the
up_logic methods. Stupid? Useful? Misguided?

Thanks,
Andrew

As a general rule, once I have a migration and the associated code
working I commit my changes. Doing that, you could simply revert your
working copy to the checkout required by the migration. Obviously that
might not be what you’re looking for if you will be migrating back and
forth often. If there’s another way, I’m not aware of it.

Out of curiosity, what are you doing with your model that’s required
for the database to reach a correct state?

If you drastically change a model, the suggested approach with schema/
logic separation might still fail since the logic might be not valid
anymore. As for the actual solution for that specific problem, it all
depends on which stage you have those migrations and what
transformations do you do in them. For example, if Test.some_method is
harmless enough to run the second time, you can delete it from the
original migration and add a new migration with the transformation
code only right after UpdateTestAgain. If that is not the case, your
options could go from creation of a special model containing the old
model code (similar to the retired model described here
Revolution On Rails: Moving models to a different database)
or, if the original migration has been applied to production,
replacing the code with straight SQL loading (see the migration
consolidation here
Revolution On Rails: DB migrations with a twist)

Hope it helps.

Val

On Jul 3, 2007, at 5:45 PM, andrew chase wrote:

class Test < ActiveRecord::Base
has_many :foo
def self.some_method
# do something useful
end
end

---- 002_update_test_class.rb
class UpdateTest < ActiveRecord::Migration

You need to add a copy of the Test model

right here (yes, inside the migration class).

class Test < ActiveRecord::Base
def self.some_method
# do something useful
end
end

  # stuff

don’t exist yet) when I’m calling it from UpdateTest migration, and it

Thanks,
Andrew

Doing the migration this way future-proofs it. You could migrate up
and down by returning all your code to the same point in time, but
the whole point of migrations is that you can drop a project into a
clean environment, create the database, run a db:migrate, and be
ready to go. I tend to put the minimal bits from the model class
into the migration and then ONLY when the model class actually needs
to be used. (Often this is to initialize the newly added table/
column.) You might also need to call Model.reset_column_information
between the time you alter the database table and try to use the
model for something so it knows about the right schema for the table.

-Rob

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