views:

5121

answers:

5

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.

+7  A: 

To go to a specified version just use:

rake db:migrate VERSION=(the version you want to go to)

But if a migration fails part way, you'll have to clean it up first. One way would be:

  • edit the down method of the migration to just undo the part of the up that worked
  • migrate back to the prior state (where you started)
  • fix the migration (including undoing your changes to the down)
  • try again
MarkusQ
Thanks. Yes, I know I could re-migrate all the way up to the failed migration, but in cases where I have a long history of migrations this can sometimes be problematic. Ideally they should execute all just fine, but more often than not I've had them fail partway, and then there's a bigger mess :-)
insane.dreamer
+10  A: 

Unfortunately, you must manually clean up failed migrations for MySQL. MySQL does not support transactional database definition changes.

Rails 2.2 includes transactional migrations for PostgreSQL. Rails 2.3 includes transactional migrations for SQLite.

This doesn't really help you for your problem right now, but if you have a choice of database on future projects, I recommend using one with support for transactional DDL because it makes migrations much more pleasant.

Luke Francl
Excellent, Thanks. I'll be doing new projects with PGSQL so it's good to know that's an option.
insane.dreamer
A: 

The easy way to do this is to wrap all of your actions in a transaction:

class WhateverMigration < ActiveRecord::Migration

 def self.up
    ActiveRecord::Base.transaction do
...
    end
  end

  def self.down
    ActiveRecord::Base.transaction do
...
    end
  end

end

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.

BryanH
It's not a question of MyISAM or InnoDB. InnoDB supports transactions, but it doesn't support transactional database definition (DDL) changes. In PostgreSQL you can drop a table and then roll back that change!
Luke Francl
A: 

Run just the down migration from the console:

http://gilesbowkett.blogspot.com/2007/07/how-to-use-migrations-from-console.html (click through to his pastie)

Ivanoats
A: 

I had a typo (in "add_column"):

def self.up

add_column :medias, :title, :text
add_colunm :medias, :enctype, :text

end

def self.down

remove_column :medias, :title
remove_column :medias, :enctype   

end

and then your problem (cannot undo partly failed migration). after some failed googling i ran this:

def self.up

remove_column :medias, :title
add_column :medias, :title, :text
add_column :medias, :enctype, :text

end

def self.down

remove_column :medias, :title
remove_column :medias, :enctype

end

as you can see i just added the correction line by hand, and then removed it again, before i checked it in.

poster