How do you roll back a failed rails migration? I would expect that rake db:rollback
would undo the failed migration, but no, it rolls back the previous migration (the failed migration minus one). And rake db:migrate:down VERSION=myfailedmigration
doesn't work either. I've ran into this a few times and it's very frustrating. Here's a simple test I made to duplicate the problem:
class SimpleTest < ActiveRecord::Migration
def self.up
add_column :assets, :test, :integer
# the following syntax error will cause the migration to fail
add_column :asset, :test2, :integer
end
def self.down
remove_column :assets, :test
remove_column :assets, :test2
end
end
result:
== SimpleTest: migrating ===================================================== -- add_column(:assets, :test, :integer) -> 0.0932s -- add_column(:asset, :error) rake aborted! An error has occurred, all later migrations canceled: wrong number of arguments (2 for 3)
ok, lets roll it back:
$ rake db:rollback == AddLevelsToRoles: reverting =============================================== -- remove_column(:roles, :level) -> 0.0778s == AddLevelsToRoles: reverted (0.0779s) ======================================
huh? that was my last migration before SimpleTest, not the failed migration. (And oh, it would be nice if the migration output included the version number.)
So lets try running the down for the failed migration SimpleTest:
$ rake db:migrate:down VERSION=20090326173033 $
Nothing happens, and no output either. But maybe it ran the migration anyway? So lets fix the syntax error in the SimpleTest migration, and try to run it again.
$ rake db:migrate:up VERSION=20090326173033 == SimpleTest: migrating ===================================================== -- add_column(:assets, :test, :integer) rake aborted! Mysql::Error: Duplicate column name 'test': ALTER TABLE `assets` ADD `test` int(11)
Nope. Obviously the migrate:down didn't work. It's not failing, it's just not executing.
No way to get rid of that duplicate table other than manually going into the database and removing it, and then running the test. There's got to be a better way than that.
The easy way to do this is to wrap all of your actions in a transaction:
As Luke Francl noted, "MySql['s MyISAM tables don't] support transactions" -- which is why you might consider avoiding MySQL in general or at least MyISAM in particular.
If you're using MySQL's InnoDB, then the above will work just fine. Any errors in either up or down will back out.
BE AWARE some types of actions cannot be reverted via transactions. Generally, table changes (dropping a table, removing or adding columns, etc.) cannot be rolled back.
I agree that you should use PostgreSQL when possible. However, when you are stuck with MySQL, you can avoid most of these problems by trying your migration on your test database first:
You can revert to the previous state and try again with
Alejandro Babio's answer above provides the best current answer.
One additional detail I want to add:
When the
myfailedmigration
migration fails, it is not considered as applied, and this can be verified by runningrake db:migrate:status
, which would show output similar to the following:The residual effect of
add_column :assets, :test, :integer
being executed on the failed migration will have to be reversed at the database level with aalter table assets drop column test;
query.