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

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

   # 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

  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

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 Biedenharn http://agileconsultingllc.com Rob@AgileConsultingLLC.com