はじめに
こんにちは、リノシスのハイパーマイグレーションクリエーター土屋です。Ruby on Railsで複数人で開発しているとブランチをマージしたらマイグレーションができなくなったというケースがあります。resetやrollbackやredoを使えば解決できることもあるかと思います。しかし、いずれの場合でもDB、テーブル、カラムを削除することになりデータを消してしまうことになるので一旦バックアップをとってから実行することになるのではないでしょうか。できればデータを移さずに変更したいですよね。本記事ではデータをresetしなくてもマイグレーションをできないか確認する方法を記述します。
結論
結論を初めに述べておくとリネームなどデータに影響がないマイグレーションだけを一時的に戻せれば安全にテーブルスキーマの変更をすることができる場合があります。以下に具体的な方法を記載します。
Railsのマイグレーション
マイグレーションが失敗する原因を探るために簡単にメカニズムを説明します。知っていたら飛ばしてください。
rails generate migration(model)
コマンド実行時にdb/migrate/{タイムスタンプ}_{クラス名}.rb
というマイグレーションファイル
が作られるrails db:migrate
コマンド実行時にマイグレーションファイル
とschema_migrations
テーブルとのタイムスタンプの差分を見てschema_migrations
テーブルにないマイグレーションファイル
が実行される実行した
マイグレーションファイル
のタイムスタンプがschema_migrations
テーブルに格納される
上記の2の差分はrails db:migrate:status
コマンドで見ることができます。
$ rails db:migrate:status database: your_database Status Migration ID Migration Name -------------------------------------------------- up 20200201000000 Create shops down 20200202010000 Create shop sales down 20200203020000 Create shop comparisons down 20200204030000 Add columns to shops up 20200205040000 Rename shops to stores up 20200206050000 Create store attributes
Statusがupのものが実行済みでdownのものが未実行になります。rails db:migrate
コマンドを実行することでdownのものがタイムスタンプが若い順に実行されます。
マイグレーションに失敗したら
では、本題のマイグレーションに失敗した場合の確認方法を説明します。
まず、マイグレーションを実行します。
$ rails db:migrate == 20200202010000 CreateShopSales: migrating ================================== -- create_table(:shop_sales, {}) -> 0.1345s == 20200202010000 CreateShopSales: migrated (1.6955s) ========================= == 20200203020000 CreateShopComparisons: migrating =========================== -- create_table(:shop_comparisons, {}) -> 0.0133s -- add_foreign_key(:shop_comparisons, :shops, {:column=>:source_shop_id}) rails aborted! StandardError: An error has occurred, this and all later migrations canceled: PG::UndefinedTable: ERROR: relation "shops" does not exist : ALTER TABLE "shop_comparisons" ADD CONSTRAINT "fk_rails_3d8e8aa96d" FOREIGN KEY ("source_shop_id") REFERENCES "shops" ("id") ...
エラー内容を見てみると上記の場合はdb/migrate/20200203020000_create_shop_comparisons.rb
でshops
というテーブルがなくてエラーになっているようです。
rails db:migrate:status
コマンドで見てみます。
$ rails db:migrate:status database: your_database Status Migration ID Migration Name -------------------------------------------------- up 20200201000000 Create shops up 20200202010000 Create shop sales down 20200203020000 Create shop comparisons down 20200204030000 Add columns to shops up 20200205040000 Rename shops to stores up 20200206050000 Create store attributes
失敗したマイグレーションの後でStatusがupになっているものを見ていくと
up 20200205040000 Rename shops to stores
という行がありファイルを見るとshops
テーブルをstores
テーブルにリネームしていました。db/migrate/20200203020000_create_shop_comparisons.rb
ではshops
テーブルを外部キーに指定していたのでエラーになっていたようです。
これを解決するためにリネームしたマイグレーションだけStatusをdownにしてテーブル名をshops
に戻してから順に実行させたいです。
$ rails db:migrate:down VERSION={Migration ID}
で実行できます。今回の場合だと以下のようになります。
$ rails db:migrate:down VERSION=20200205040000 == 20200205040000 RenameShopsToStores: reverting ============================== -- rename_table(:stores, :shops) -> 1.3139s == 20200205040000 RenameShopsToStores: reverted (1.7178s) =====================
確認してみると
$ rails db:migrate:status database: your_database Status Migration ID Migration Name -------------------------------------------------- up 20200201000000 Create shops up 20200202010000 Create shop sales down 20200203020000 Create shop comparisons down 20200204030000 Add columns to shops down 20200205040000 Rename shops to stores up 20200206050000 Create store attributes
Migration ID: 20200205040000
がdownになっています。
これで再度実行します。
$ rails db:migrate == 20200203020000 CreateShopComparisons: migrating =========================== -- create_table(:shop_comparisons, {}) -> 0.3016s -- add_foreign_key(:shop_comparisons, :shops, {:column=>:source_shop_id}) -> 0.3044s -- add_foreign_key(:shop_comparisons, :shops, {:column=>:target_shop_id}) -> 0.3043s == 20200203020000 CreateShopComparisons: migrated (3.1383s) ================== == 20200204030000 AddColumnsToShops: migrating ================================ -- add_column(:shops, :all_seats, :integer) -> 0.0024s -- add_column(:shops, :non_smoking_seats, :integer) -> 0.0020s -- add_column(:shops, :store_type, :integer) -> 0.0028s -- add_column(:shops, :prefecture_id, :integer) -> 0.0023s == 20200204030000 AddColumnsToShops: migrated (0.0097s) ======================= == 20200205040000 RenameShopsToStores: migrating ============================== -- rename_table(:shops, :stores) -> 0.0184s == 20200205040000 RenameShopsToStores: migrated (0.0185s) =====================
$ rails db:migrate:status database: your_database Status Migration ID Migration Name -------------------------------------------------- up 20200201000000 Create shops up 20200202010000 Create shop sales up 20200203020000 Create shop comparisons up 20200204030000 Add columns to shops up 20200205040000 Rename shops to stores up 20200206050000 Create store attributes
成功しました。このようにresetしなくてもあるマイグレーションだけ一時的に巻き戻して順番を変えるだけで良いケースがあるので、まずは失敗している要因を探ってみても良いかもしれません。
宣伝
RenoSysではマイグレーションできます