Migrations are great. Not perfect but pretty damn good. Certainly better than my previous experiences of handling databases (grab the structure from dev, compare it to qa and then construct a script to move from one to the other - if you are lucky with a tool like Sql Compare, if you're not then by hand).
But there is a hidden time bomb in there. You see, when you are writing your migration you, your migration and your models are at a particular point in time. But when you deploy your application your migration and your models are at different moments in time. Confused?
Let's run through an example. It's August 2007 and your production application is on migration version 12. Your model files, dated August 2007, have lots of nice (unit-tested) validations all set up and ready to go. Fast forward to October 2007. Your production application is still on migration 12 but your development version is on migration 18. Your subversion repository contains a load of model files dated October 2007. It's time to deploy. You type cap deploy_with_migrations and the migrations fall over at the data insert script, number 13 (unlucky eh?).
So what's going on?
The thing to remember is that when you are writing the code, the migration and the model are in sync. But when you come to deploy the code they no longer are. cap deploy_with_migrations grabs the October model files out of svn and then runs the migrations one at a time (after inspecting schema_info) - starting with migration 11, then 12, then 13 then BOOM! The problem is that migration 13 is dated September 2007 and is expecting the models from September 2007. But svn has just faithfully grabbed the models from October. If migration 17 adds a new field and the October model has a validation that enforces a rule on that new field - when migration 13 tries to instantiate and save an instance of that class, the (future) validation rule fires and fails. As the field does not yet exist.
So how do we solve this?
It's a bit of a hack but we can rely on Ruby's dynamic nature. Just stick a dummy class declaration at the top of your migration file whenever you need to do data inserts.
class MyModel < ActiveRecord::Base
class MyMigration13 < ActiveRecord::Migration
MyModel.create :field => 'value'
When MyModel.create is called, it is using your "locally" defined ActiveRecord::Base descendant and not the one in app/models - meaning that whatever validations you have applied (in the future) do not apply. Of course this also means you can screw up the data - but, hey, nobody's perfect!
Polar bears by Marja Flick-Buijs on stock.xchng