Revertir una migración fallida de Rails

82

¿Cómo revertir una migración de rieles fallida? Esperaría que rake db:rollbackdeshaga la migración fallida, pero no, revierte la migración anterior (la migración fallida menos uno). Y rake db:migrate:down VERSION=myfailedmigrationtampoco funciona. Me he encontrado con esto varias veces y es muy frustrante. Aquí hay una prueba simple que hice para duplicar el problema:

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

resultado:

== SimpleTest: migrando ============================================= ========
- add_column (: activos,: prueba,: entero)
   -> 0.0932 s
- add_column (: activo,: error)
rastrillo abortado!
Ha ocurrido un error, todas las migraciones posteriores canceladas:

número incorrecto de argumentos (2 por 3)

ok, retrocedamos:

$ rake db: rollback
== AddLevelsToRoles: revirtiendo =========================================== ==
- remove_column (: roles,: level)
   -> 0.0778 s
== AddLevelsToRoles: revertido (0.0779s) ======================================

eh esa fue mi última migración antes de SimpleTest, no la migración fallida. (Y, oh, sería bueno si el resultado de la migración incluyera el número de versión).

Así que intentemos ejecutar el down para la migración fallida SimpleTest:

$ rake db: migrate: down VERSION = 20090326173033
PS

No pasa nada y tampoco hay salida. ¿Pero tal vez ejecutó la migración de todos modos? Así que corrijamos el error de sintaxis en la migración de SimpleTest e intentemos ejecutarlo nuevamente.

$ rake db: migrate: up VERSION = 20090326173033
== SimpleTest: migrando =========================================== ========
- add_column (: activos,: prueba,: entero)
rastrillo abortado!
Mysql :: Error: Nombre de columna duplicado 'prueba': ALTER TABLE `assets` ADD` test` int (11)

No Obviamente, migrate: down no funcionó. No está fallando, simplemente no se está ejecutando.

No hay forma de deshacerse de esa tabla duplicada que no sea ingresar manualmente a la base de datos y eliminarla, y luego ejecutar la prueba. Tiene que haber una forma mejor que esa.

loco soñador
fuente

Respuestas:

79

Desafortunadamente, debe limpiar manualmente las migraciones fallidas para MySQL. MySQL no admite cambios en la definición de bases de datos transaccionales.

Rails 2.2 incluye migraciones transaccionales para PostgreSQL. Rails 2.3 incluye migraciones transaccionales para SQLite.

Esto realmente no le ayuda con su problema en este momento, pero si tiene la opción de elegir una base de datos en proyectos futuros, le recomiendo usar una con soporte para DDL transaccional porque hace que las migraciones sean mucho más agradables.

Actualización: esto sigue siendo cierto en 2017, en Rails 4.2.7 y MySQL 5.7, informado por Alejandro Babio en otra respuesta aquí.

Luke Francl
fuente
1
Excelente, gracias. Haré nuevos proyectos con PGSQL, así que es bueno saber que es una opción.
insano soñador
Esta sigue siendo la mejor respuesta, por lo que merece la recompensa en mi humilde opinión.
nathanvda
20

Para ir a una versión específica, simplemente use:

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

Pero si una migración falla parcialmente, primero tendrá que limpiarla. Una forma sería:

  • edite el downmétodo de la migración para deshacer la parte upque funcionó
  • migrar de nuevo al estado anterior (donde comenzó)
  • arreglar la migración (incluyendo deshacer sus cambios en down)
  • Inténtalo de nuevo
MarkusQ
fuente
Gracias. Sí, sé que podría volver a migrar hasta la migración fallida, pero en los casos en los que tengo un largo historial de migraciones, esto a veces puede ser problemático. Idealmente deben ejecutar todas bien sólo, pero más a menudo que no he tenido que dejar a medias, y luego hay un lío más grande :-)
insane.dreamer
20

Bien, amigos, así es como realmente lo hacen. No sé de qué están hablando las respuestas anteriores.

  1. Averigüe qué parte de la migración ascendente funcionó. Comenta esos.
  2. También comente / elimine la parte de la migración que se rompió.
  3. Ejecute la migración nuevamente. Ahora completará las partes no rotas de la migración, omitiendo las partes que ya se han hecho.
  4. Descomente las partes de la migración que comentó en el paso 1.

Puede migrar hacia abajo y realizar una copia de seguridad nuevamente si desea verificar que lo tiene ahora.

Simon Woodside
fuente
2
Hago algo muy similar, pero reemplazo el paso 2 con "arreglar la parte de la migración que se rompió".
Don Kirkby
2
Vale la pena enfatizar el último punto: correr bundle exec rake db:migrate:redo. Dará un paso hacia atrás y un paso hacia adelante, para que pueda verificar que su última migración se ejecuta completamente. Esta es una buena práctica siempre que tenga que impulsar una migración junto con algunas actualizaciones de código.
mahemoff
12

Estoy de acuerdo en que debería usar PostgreSQL cuando sea posible. Sin embargo, cuando está atascado con MySQL, puede evitar la mayoría de estos problemas probando primero su migración en su base de datos de prueba:

rake db:migrate RAILS_ENV=test

Puede volver al estado anterior e intentarlo de nuevo con

rake db:schema:load RAILS_ENV=test
StefanH
fuente
Más una solución que una respuesta, pero esta es una buena idea que no se me había ocurrido antes.
Emily
10

En 2015 con Rails 4.2.1 y MySQL 5.7, una migración fallida no se puede solucionar con las acciones de rake estándar que proporciona Rails, como sucedió en 2009.

MySql no admite la reversión de declaraciones DDL (en el Manual de MySQL 5.7 ). Y Rails no puede hacer nada con eso.

Además, podemos comprobar cómo Rails está haciendo el trabajo: una migración está envuelta en una transacción dependiendo de cómo responda el adaptador de conexión :supports_ddl_transactions?. Después de una búsqueda de esta acción en la fuente de rieles (v 4.2.1), descubrí que solo Sqlite3 y PostgreSql admiten transacciones y, de forma predeterminada , no son compatibles.

Editar Por lo tanto, la respuesta actual a la pregunta original: una migración de MySQL fallida debe arreglarse manualmente.

Alejandro Babio
fuente
No entiendo bien esta respuesta: excepto al actualizar los números de versión, no agrega nada a la respuesta aceptada original.
nathanvda
1
Muy cierto, para la pregunta original. Para la recompensa que comenzó por Andrew Grimm: "Quiero saber si la situación ha cambiado desde que se hizo la pregunta en marzo de 2009". Es una respuesta actual y proporciona un método para verificar cualquier cambio en el futuro.
Alejandro Babio
8

La forma más sencilla de hacer esto es incluir todas sus acciones en una transacción:

class WhateverMigration < ActiveRecord::Migration

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

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

end

Como señaló Luke Francl, "MySql [las tablas MyISAM no] admiten transacciones", por lo que podría considerar evitar MySQL en general o al menos MyISAM en particular.

Si está utilizando InnoDB de MySQL, lo anterior funcionará bien. Cualquier error en up o down se revertirá.

TENGA EN CUENTA que algunos tipos de acciones no se pueden revertir mediante transacciones. Por lo general, los cambios de tabla (eliminar una tabla, eliminar o agregar columnas, etc.) no se pueden deshacer.

BryanH
fuente
5
No se trata de MyISAM o InnoDB. InnoDB admite transacciones, pero no admite cambios en la definición de base de datos transaccional (DDL). En PostgreSQL, puede eliminar una tabla y luego revertir ese cambio.
Luke Francl
1
Luke tiene razón, mysql no admite transacciones en cambios de DDL. Tengo que considerar la limpieza por mí mismo, como agregar y eliminar una columna de las tablas.
Leon Guan
1

Tuve un error tipográfico (en "add_column"):

def self.up

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

fin

def self.down

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

fin

y luego su problema (no se puede deshacer la migración parcialmente fallida). después de algunas búsquedas fallidas en Google, ejecuté esto:

def self.up

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

fin

def self.down

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

fin

como puede ver, acabo de agregar la línea de corrección a mano y luego la quité nuevamente, antes de registrarla.

póster
fuente
1

La respuesta de Alejandro Babio anterior proporciona la mejor respuesta actual.

Un detalle adicional que quiero agregar:

Cuando la myfailedmigrationmigración falla, no se considera aplicada, y esto se puede verificar ejecutando rake db:migrate:status, lo que mostraría un resultado similar al siguiente:

$  rake db:migrate:status
database: sample_app_dev

 Status   Migration ID    Migration Name
--------------------------------------------------
   up      20130206203115  Create users
   ...
   ...
   down    20150501173156  Test migration

El efecto residual de add_column :assets, :test, :integerser ejecutado en la migración fallida tendrá que revertirse a nivel de base de datos con una alter table assets drop column test;consulta.

Prakash Murthy
fuente