リノシス開発者ブログ

株式会社リノシスのエンジニアブログです。

rails db:resetをする前に確認すべきこと

はじめに

こんにちは、リノシスのハイパーマイグレーションリエーター土屋です。Ruby on Railsで複数人で開発しているとブランチをマージしたらマイグレーションができなくなったというケースがあります。resetやrollbackやredoを使えば解決できることもあるかと思います。しかし、いずれの場合でもDB、テーブル、カラムを削除することになりデータを消してしまうことになるので一旦バックアップをとってから実行することになるのではないでしょうか。できればデータを移さずに変更したいですよね。本記事ではデータをresetしなくてもマイグレーションをできないか確認する方法を記述します。

結論

結論を初めに述べておくとリネームなどデータに影響がないマイグレーションだけを一時的に戻せれば安全にテーブルスキーマの変更をすることができる場合があります。以下に具体的な方法を記載します。

Railsマイグレーション

マイグレーションが失敗する原因を探るために簡単にメカニズムを説明します。知っていたら飛ばしてください。

  1. rails generate migration(model)コマンド実行時にdb/migrate/{タイムスタンプ}_{クラス名}.rbというマイグレーションファイルが作られる

  2. rails db:migrateコマンド実行時にマイグレーションファイルschema_migrationsテーブルとのタイムスタンプの差分を見てschema_migrationsテーブルにないマイグレーションファイルが実行される

  3. 実行したマイグレーションファイルのタイムスタンプが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.rbshopsというテーブルがなくてエラーになっているようです。

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ではマイグレーションできます

en-gage.net