Use db:migrate:redo while properly handling exceptions from schema changes
I have a migration that's breaking in the middle of a couple of schema changes. When it breaks an exception is thrown and rake db:migrate exits, leaving my database in a half-migrated state.
How can I set it up so that the migration automatically reverts just the changes that have run so far? I'd like to do so globally when in development mode. Surely someone out there has a better way than embedding each successive AR::Migration::ClassMethod
in a begin; rescue =>e opposite_action; end
block.
Perhaps a common example is in order:
#2010010100000000_made_a_typo.rb
class MadeATypo < ActiveRecord::Migration
def self.up
rename_column :birds, :url, :photo_file_name
rename_column :birds, :genius, :species #typo on :genius => :genus
end
def self.down
开发者_StackOverflow rename_column :birds, :photo_file_name, :url
rename_column :birds, :species, :genius
end
end
This migration will fail on the second line with "column genius not found", but not record the migration number in the schema_migrations table. I'd like it if it called
rename_column :birds, :photo_file_name, :url #this is a revert of the first line
before the exception was passed out of MadeATypo.up
.
Responses to comments:
I understand that mysql might not have support for DDL transactions, I'm looking for a more application-level solution which (probably) uses AR::Migration itself. Surely someone has created a plugin which captures method calls to the main AR:M:ClassMethods and can rewind them in most cases if an exception occurs during a migration.
I don't have a solution, but the main problem is that DDL statements can't be transactioned (at least in MySQL, I don't know if that's a general thing).
So, because migrations are treated as atomic up/down actions, there's no easy way to undo "half" a migration - it's not easy to work out which parts of the down
correspond to which parts of the up
I don't know of any way to do this currently, but the code for reversible migrations coming to Rails 3.1 looks like a good base to build this feature on. See these links:
- http://edgerails.info/articles/what-s-new-in-edge-rails/2011/05/06/reversible-migrations/index.html
- https://github.com/rails/rails/commit/47017bd1697d6b4d6780356a403f91536eacd689
- https://github.com/rails/rails/blob/master/activerecord/lib/active_record/migration/command_recorder.rb
The way I'm thinking you could implement this is to run the migration against the recorder (as in https://github.com/rails/rails/commit/47017bd1697d6b4d6780356a403f91536eacd689#L0R337), then switch back to the live connection and run the recorder forward one command at a time, tracking how many have completed. All you need, then, is to wrap that forward running loop in a begin-rescue-end that executes as many inverse commands as you successfully executed forward commands, if there's an exception.
精彩评论