Откат неудачной миграции Rails

82

Как отменить неудачную миграцию рельсов? Я ожидал, что rake db:rollbackэто отменит неудачную миграцию, но нет, это откатит предыдущую миграцию (неудачная миграция минус один). И rake db:migrate:down VERSION=myfailedmigrationтоже не работает. Я сталкивался с этим несколько раз, и это очень расстраивает. Вот простой тест, который я провел, чтобы воспроизвести проблему:

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

результат:

== SimpleTest: перенос ============================================= ========
- add_column (: assets,: test,: integer)
   -> 0,0932 с
- add_column (: актив,: ошибка)
грабли прерваны!
Произошла ошибка, все последующие миграции отменены:

неправильное количество аргументов (2 вместо 3)

ок, давай откатим назад:

$ rake db: откат
== AddLevelsToRoles: возврат =============================================== ==
- remove_column (: роли,: уровень)
   -> 0,0778 с
== AddLevelsToRoles: возвращено (0,0779 с) ======================================

а? это была моя последняя миграция перед SimpleTest, а не неудачная миграция. (И о, было бы неплохо, если бы вывод миграции содержал номер версии.)

Итак, давайте попробуем запустить простой тест для неудачной миграции:

$ rake db: migrate: down ВЕРСИЯ = 20090326173033
$

Ничего не происходит, и выхода тоже нет. Но, может быть, он все равно выполнил миграцию? Итак, давайте исправим синтаксическую ошибку в миграции SimpleTest и попробуем запустить ее снова.

$ rake db: migrate: up VERSION = 20090326173033
== SimpleTest: перенос ============================================= ========
- add_column (: assets,: test,: integer)
грабли прерваны!
Mysql :: Ошибка: повторяющееся имя столбца 'test': ALTER TABLE `assets` ADD` test` int (11)

Нет. Очевидно, что migrate: down не сработал. Это не сбой, это просто не выполняется.

Нет другого способа избавиться от этой повторяющейся таблицы, кроме как вручную войти в базу данных и удалить ее, а затем запустить тест. Должен быть способ получше.

безумный мечтатель
источник

Ответы:

79

К сожалению, вы должны вручную очищать неудачные миграции для MySQL. MySQL не поддерживает изменения определения транзакционной базы данных.

Rails 2.2 включает транзакционные миграции для PostgreSQL. Rails 2.3 включает транзакционные миграции для SQLite.

На самом деле это не поможет вам в решении вашей проблемы прямо сейчас, но если у вас есть выбор базы данных для будущих проектов, я рекомендую использовать базу данных с поддержкой транзакционного DDL, потому что это делает миграции намного более приятными.

Обновление - это все еще актуально в 2017 году для Rails 4.2.7 и MySQL 5.7, о чем Алехандро Бабио сообщил в другом ответе здесь.

Люк Франкл
источник
1
Отлично, спасибо. Я буду делать новые проекты с PGSQL, так что хорошо знать, что это вариант.
insane.dreamer
Это по-прежнему лучший ответ, так что он заслуживает щедрости, имхо.
nathanvda 03
20

Чтобы перейти к указанной версии, просто используйте:

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

Но если миграция не удается частично, вам придется сначала очистить ее. Один из способов:

  • отредактируйте downметод миграции, чтобы просто отменить ту часть, upкоторая сработала
  • вернуться в предыдущее состояние (с которого вы начали)
  • исправить миграцию (в том числе отменить изменения в down)
  • Попробуйте снова
MarkusQ
источник
Благодарю. Да, я знаю, что могу выполнить повторную миграцию вплоть до неудавшейся миграции, но в тех случаях, когда у меня долгая история миграций, это иногда может быть проблематичным. В идеале они должны выполнять все безупречно, но чаще всего они терпят неудачу на полпути, и тогда возникает еще больший беспорядок :-)
insane.dreamer
20

Хорошо, ребята, вот как вы это делаете. Я не знаю, о чем говорят приведенные выше ответы.

  1. Выясните, какая часть миграции сработала. Прокомментируйте их.
  2. Также закомментируйте / удалите ту часть миграции, которая сломалась.
  3. Снова запустите миграцию. Теперь он завершит неразрывные части миграции, пропуская части, которые уже были выполнены.
  4. Раскомментируйте фрагменты миграции, которые вы закомментировали на шаге 1.

Вы можете выполнить миграцию вниз и снова вверх, если хотите убедиться, что у вас это есть прямо сейчас.

Саймон Вудсайд
источник
2
Я делаю нечто очень похожее, но заменяю шаг 2 на «исправить часть миграции, которая сломалась».
Дон Киркби
2
Стоит подчеркнуть последний пункт - бег bundle exec rake db:migrate:redo. Он будет делать один шаг назад и один шаг вперед, так что вы можете убедиться, что ваша последняя миграция выполняется полностью. Это хорошая практика в любое время, когда вам нужно продвинуть миграцию вместе с некоторыми обновлениями кода.
mahemoff
12

Я согласен с тем, что вам следует использовать PostgreSQL, когда это возможно. Однако, когда вы застряли в MySQL, вы можете избежать большинства этих проблем, попробовав сначала выполнить миграцию в тестовой базе данных:

rake db:migrate RAILS_ENV=test

Вы можете вернуться к предыдущему состоянию и повторить попытку с помощью

rake db:schema:load RAILS_ENV=test
StefanH
источник
Скорее обходной путь, чем ответ, но это хорошая идея, которая раньше мне не приходила в голову.
Эмили
10

В 2015 году с Rails 4.2.1 и MySQL 5.7 неудачная миграция не могла быть исправлена ​​стандартными граблями, которые предоставляет Rails, как это было в 2009 году.

MySql не поддерживает откат статусов DDL (в Руководстве по MySQL 5.7 ). И Rails с этим ничего не может поделать.

Кроме того, мы можем проверить, как Rails выполняет свою работу: миграция заключена в транзакцию в зависимости от того, как на нее отвечает адаптер подключения :supports_ddl_transactions?. После поиска этого действия в источнике rails (v 4.2.1) я обнаружил, что только Sqlite3 и PostgreSql поддерживают транзакции, и по умолчанию они не поддерживаются.

Изменить Таким образом, текущий ответ на исходный вопрос: неудачная миграция MySQL должна быть исправлена ​​вручную.

Алехандро Бабио
источник
Я не совсем понимаю этот ответ: кроме обновления номеров версий, он ничего не добавляет к исходному принятому ответу.
nathanvda
1
Совершенно верно для исходного вопроса. Для Эндрю Гримма началось вознаграждение: «Хотите знать, изменилась ли ситуация с тех пор, как вопрос был задан в марте 2009 года». Это текущий ответ, который дает возможность проверить любые изменения в будущем.
Алехандро Бабио
8

Самый простой способ сделать это - обернуть все ваши действия в транзакцию:

class WhateverMigration < ActiveRecord::Migration

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

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

end

Как заметил Люк Франкл, «таблицы MySql [MyISAM] не поддерживают] транзакции» - вот почему вы можете подумать об отказе от MySQL в целом или, по крайней мере, MyISAM в частности.

Если вы используете MySQL InnoDB, то вышеуказанное будет работать нормально. Любые ошибки вверх или вниз вернутся.

ВНИМАНИЕ: некоторые действия нельзя отменить с помощью транзакций. Как правило, изменения таблицы (удаление таблицы, удаление или добавление столбцов и т. Д.) Не могут быть отменены.

BryanH
источник
5
Это не вопрос MyISAM или InnoDB. InnoDB поддерживает транзакции, но не поддерживает изменения определения транзакционной базы данных (DDL). В PostgreSQL вы можете отбросить таблицу, а затем откатить это изменение!
Люк Франкл,
1
Люк прав, mysql не поддерживает транзакцию при изменении DDL. Я должен сам подумать об очистке, такой как добавление и удаление столбца из таблиц.
Леон Гуан
1

У меня была опечатка (в "add_column"):

def self.up

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

конец

def self.down

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

конец

а затем ваша проблема (нельзя отменить частично неудачную миграцию). после неудачного поиска в Google я запустил это:

def self.up

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

конец

def self.down

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

конец

как вы можете видеть, я просто вручную добавил строку коррекции, а затем снова удалил ее, прежде чем зарегистрировал ее.

плакат
источник
1

Ответ Алехандро Бабио выше дает лучший текущий ответ.

Еще одна деталь, которую я хочу добавить:

Если myfailedmigrationмиграция не выполняется, она не считается примененной, и это можно проверить, запустив ее rake db:migrate:status, и в результате будут выведены следующие данные:

$  rake db:migrate:status
database: sample_app_dev

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

Остаточное влияние add_column :assets, :test, :integerвыполнения на неудачную миграцию необходимо будет отменить на уровне базы данных с помощью alter table assets drop column test;запроса.

Пракаш Мурти
источник