Wycofywanie nieudanej migracji Railsów

82

Jak przywrócić nieudaną migrację szyn? Spodziewałbym się, rake db:rollbackże cofnie to nieudaną migrację, ale nie, cofa poprzednią migrację (nieudana migracja minus jedna). I rake db:migrate:down VERSION=myfailedmigrationteż nie działa. Spotkałem się z tym kilka razy i jest to bardzo frustrujące. Oto prosty test, który wykonałem, aby zduplikować 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

wynik:

== SimpleTest: migracja ============================================= ========
- add_column (: asset,: test,: integer)
   -> 0,0932 s
- add_column (: asset,: error)
prowizja przerwana!
Wystąpił błąd, wszystkie późniejsze migracje zostały anulowane:

zła liczba argumentów (2 za 3)

ok, cofnijmy to:

$ rake db: rollback
== AddLevelsToRoles: przywracanie =============================================== ==
- remove_column (: role,: level)
   -> 0,0778 s
== AddLevelsToRoles: przywrócone (0,0779s) ========================================

co? to była moja ostatnia migracja przed SimpleTest, a nie nieudana migracja. (I och, byłoby miło, gdyby dane wyjściowe migracji zawierały numer wersji).

Spróbujmy więc uruchomić wyłączenie dla nieudanej migracji SimpleTest:

$ rake db: migrate: down WERSJA = 20090326173033
$

Nic się nie dzieje i nie ma też wyjścia. Ale może i tak przeprowadził migrację? Więc naprawmy błąd składni w migracji SimpleTest i spróbujmy go ponownie uruchomić.

$ rake db: migrate: up WERSJA = 20090326173033
== SimpleTest: migracja ============================================= ========
- add_column (: asset,: test,: integer)
prowizja przerwana!
MySQL :: Błąd: zduplikowana nazwa kolumny 'test': ALTER TABLE `asset` ADD` test` int (11)

Nie. Oczywiście migrate: down nie zadziałało. Nie zawodzi, po prostu się nie wykonuje.

Nie ma innego sposobu na pozbycie się tej zduplikowanej tabeli niż ręczne wejście do bazy danych i usunięcie jej, a następnie uruchomienie testu. Musi być lepszy sposób niż to.

insane.dreamer
źródło

Odpowiedzi:

79

Niestety, musisz ręcznie wyczyścić nieudane migracje MySQL. MySQL nie obsługuje transakcyjnych zmian definicji bazy danych.

Rails 2.2 zawiera migracje transakcyjne dla PostgreSQL. Rails 2.3 zawiera migracje transakcyjne dla SQLite.

W tej chwili nie pomaga to w rozwiązaniu problemu, ale jeśli masz wybór bazy danych na temat przyszłych projektów, polecam korzystanie z takiej bazy danych z obsługą transakcyjnego DDL, ponieważ znacznie ułatwia to migracje.

Aktualizacja - tak jest nadal w 2017 roku na Railsach 4.2.7 i MySQL 5.7, o czym poinformował Alejandro Babio w innej odpowiedzi tutaj.

Luke Francl
źródło
1
Wspaniale, dziękuje. Będę robił nowe projekty z PGSQL, więc dobrze wiedzieć, że jest to opcja.
insane.dreamer
To wciąż najlepsza odpowiedź, więc to zasługuje na nagrodę imho.
nathanvda
20

Aby przejść do określonej wersji, użyj:

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

Ale jeśli migracja nie powiedzie się częściowo, musisz ją najpierw wyczyścić. Jednym ze sposobów byłoby:

  • edytuj downmetodę migracji, aby po prostu cofnąć tę część, upktóra działała
  • migrować z powrotem do poprzedniego stanu (gdzie zacząłeś)
  • napraw migrację (w tym cofnięcie zmian w pliku down )
  • Spróbuj ponownie
MarkusQ
źródło
Dzięki. Tak, wiem, że mogę przeprowadzić ponowną migrację aż do migracji zakończonej niepowodzeniem, ale w przypadkach, w których mam długą historię migracji, może to czasami być problematyczne. Idealnie byłoby, gdyby wykonali wszystko dobrze, ale częściej niż nie zdarzało mi się, że zawodziły częściowo, a potem jest większy bałagan :-)
insane.dreamer
20

OK, ludzie, oto jak to robicie. Nie wiem, o czym mówią powyższe odpowiedzi.

  1. Dowiedz się, która część migracji w górę zadziałała. Skomentuj je.
  2. Skomentuj także / usuń część migracji, która się zepsuła.
  3. Ponownie uruchom migrację. Teraz zakończy niezepsute części migracji, pomijając części, które już zostały wykonane.
  4. Odkomentuj fragmenty migracji, które zakomentowałeś w kroku 1.

Możesz przeprowadzić migrację w dół i ponownie utworzyć kopię zapasową, jeśli chcesz sprawdzić, czy masz ją teraz.

Simon Woodside
źródło
2
Robię coś bardzo podobnego, ale zamieniam krok 2 na „napraw część migracji, która się zepsuła”.
Don Kirkby,
2
Warto podkreślić ostatni punkt - bieg bundle exec rake db:migrate:redo. Będzie to krok wstecz i krok do przodu, dzięki czemu możesz sprawdzić, czy ostatnia migracja przebiega przez cały czas. Jest to dobra praktyka za każdym razem, gdy musisz wypchnąć migrację wraz z niektórymi aktualizacjami kodu.
mahemoff
12

Zgadzam się, że powinieneś używać PostgreSQL, jeśli to możliwe. Jeśli jednak utkniesz z MySQL, możesz uniknąć większości tych problemów, próbując najpierw przeprowadzić migrację na testowej bazie danych:

rake db:migrate RAILS_ENV=test

Możesz powrócić do poprzedniego stanu i spróbować ponownie za pomocą

rake db:schema:load RAILS_ENV=test
StefanH
źródło
Bardziej obejście niż odpowiedź, ale to dobry pomysł, który wcześniej nie przyszedł mi do głowy.
Emily
10

W 2015 roku z Railsami 4.2.1 i MySQL 5.7 nieudanej migracji nie można naprawić za pomocą standardowych akcji rake, które zapewniają Railsy, ​​tak jak to było w 2009 roku.

MySql nie obsługuje wycofywania instrukcji DDL (w podręczniku MySQL 5.7 ). A Railsy nie mogą nic z tym zrobić.

Możemy również sprawdzić, jak Railsy wykonują swoją pracę: migracja jest opakowana w transakcję w zależności od odpowiedzi adaptera połączenia :supports_ddl_transactions?. Po przeszukaniu tej akcji w źródłach railsowych (v 4.2.1) stwierdziłem, że tylko Sqlite3 i PostgreSql obsługują transakcje i domyślnie nie są obsługiwane.

Edycja Zatem aktualna odpowiedź na pierwotne pytanie: Nieudana migracja MySQL musi zostać naprawiona ręcznie.

Alejandro Babio
źródło
Nie do końca rozumiem tę odpowiedź: poza aktualizacją numerów wersji nic nie dodaje do pierwotnie zaakceptowanej odpowiedzi.
nathanvda
1
Bardzo prawdziwe, jeśli chodzi o pierwotne pytanie. Bo nagroda rozpoczęła się dla Andrew Grimma: „Chcesz wiedzieć, czy sytuacja uległa zmianie od czasu zadania pytania w marcu 2009”. Jest to aktualna odpowiedź i daje metodę sprawdzania wszelkich zmian w przyszłości.
Alejandro Babio
8

Najłatwiejszym sposobem na to jest zawarcie wszystkich działań w transakcji:

class WhateverMigration < ActiveRecord::Migration

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

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

end

Jak zauważył Luke Francl, „Tabele MySql [w MyISAM] nie obsługują transakcji” - dlatego warto rozważyć ogólne unikanie MySQL lub przynajmniej MyISAM w szczególności.

Jeśli używasz InnoDB MySQL, powyższe będzie działać dobrze. Wszelkie błędy w górę lub w dół zostaną wycofane.

UWAŻAJ na niektóre rodzaje działań nie można cofnąć za pośrednictwem transakcji. Ogólnie rzecz biorąc, zmian w tabeli (upuszczanie tabeli, usuwanie lub dodawanie kolumn itp.) Nie można cofnąć.

BryanH
źródło
5
To nie jest kwestia MyISAM czy InnoDB. InnoDB obsługuje transakcje, ale nie obsługuje zmian definicji transakcyjnej bazy danych (DDL). W PostgreSQL możesz usunąć tabelę, a następnie cofnąć tę zmianę!
Luke Francl
1
Luke ma rację, mysql nie obsługuje transakcji na zmianach DDL. Muszę wziąć pod uwagę samodzielne czyszczenie, takie jak dodanie i usunięcie kolumny z tabel.
Leon Guan
1

Wystąpiła literówka (w „add_column”):

def self.up

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

koniec

def self.down

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

koniec

a następnie problem (nie można cofnąć częściowo nieudanej migracji). po kilku nieudanych googlach uruchomiłem to:

def self.up

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

koniec

def self.down

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

koniec

jak widać, po prostu ręcznie dodałem linię korekty, a następnie usunąłem ją ponownie, zanim ją sprawdziłem.

plakat
źródło
1

Odpowiedź Alejandro Babio powyżej zapewnia najlepszą aktualną odpowiedź.

Jeszcze jeden szczegół, który chcę dodać:

Gdy myfailedmigrationmigracja się nie powiedzie, nie jest uznawana za zastosowaną i można to zweryfikować, uruchamiając rake db:migrate:status, co spowoduje wyświetlenie wyników podobnych do następujących:

$  rake db:migrate:status
database: sample_app_dev

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

Pozostały efekt add_column :assets, :test, :integerwykonania nieudanej migracji będzie musiał zostać odwrócony na poziomie bazy danych za pomocą alter table assets drop column test;zapytania.

Prakash Murthy
źródło