schema.rbで差分が発生する事例とその復旧について
Railsには、スキーマファイルと呼ばれる schema.rb
があります。
Railsガイドには
Active Recordはマイグレーションの時系列に沿ってスキーマを更新する方法を知っているので、履歴のどの時点からでも最新バージョンのスキーマに更新できます。Active Recordは
db/schema.rb
ファイルを更新し、データベースの最新の構造と一致するようにします。
とあるように、schema.rb
はデータベース関連の機能で使われます。
その schema.rb
ですが、チームで開発をしている時に
自分の作業を中断して、プルリクエストのレビューをする
自分の作業に戻って、開発を続ける
を繰り返している中で、意図しない差分が schema.rb
に発生していると気づき、戸惑うことがあるかもしれません。
そこで、schema.rb
に差分が発生してしまうことについて
発生する事例
差分が発生していると何が起こるか
どう復旧するか
を記事としてまとめます。
発生する事例
発生する事例として、「どのような流れで発生してしまうのか」を体験できるチュートリアルを用意しました。
チュートリアルは以下の流れで進めます。なお、Railsのバージョンは 7.0.4.2
を想定しています。
mainブランチで、Railsのモデルを作成
mainから派生した第2のブランチ(
feature/add_unique_index
)で、モデルの変更を伴うマイグレーションを適用mainから派生した第3のブランチ(
feature/add_column
)で、モデルの変更を伴うマイグレーションを適用
1. mainブランチで、Railsのモデルを作成
最小構成で rails new
します。後ほどgemを追加するため、現時点では bundle install
を行いません。
今回の動作確認をRSpecで行うため、 Gemfile
の末尾に追加します。
Gemfileの準備ができたため、一連のgemをインストールします。
RSpecのセットアップも行います。
続いてモデルを用意します。今回は isbn
列を持つBookモデルとします。
マイグレーション適用後に、状態を確認しておきます。
ここまでをコミットします。
(もし vendor/bundle
以下をコミットしたくない場合は .gitignore
に追加しておきます)
2. mainから派生した第2のブランチ(feature/add_unique_index
)で、モデルの変更を伴うマイグレーションを適用
feature/add_unique_index
)で、モデルの変更を伴うマイグレーションを適用main
ブランチから第2のブランチ( feature/add_unique_index
)を新しく作成し、「Bookの isbn
にUNIQUE制約を追加する」ためのマイグレーションファイルを生成します。
生成したマイグレーションファイルにて、Bookの isbn
にUNIQUE制約を追加します。
マイグレーションを適用後、状況を確認します。
作業が終わったため、コミットします。
3. mainから派生した第3のブランチ(feature/add_column
)で、モデルの変更を伴うマイグレーションを適用
feature/add_column
)で、モデルの変更を伴うマイグレーションを適用ここからが、意図しない差分を発生させてしまう手順になります。
本来ならば、第2のブランチ(feature/add_unique_index
)を離れる前にマイグレーションをロールバックします。
ただ、今回は schema.rb
に差分を発生させるため、マイグレーションを適用したまま、
第3のブランチ(
feature/add_column
)を新しく作成・切り替えモデルに列
name
を追加マイグレーションを適用
を行います。
続いて、マイグレーションの状態を確認します。作業2.のマイグレーションが適用されたまま、今回のマイグレーションも適用されているのが分かります。
schema.rb
を見ると isbn
列にUNIQUE制約が定義されており、意図しない状態となっています。
最後に、「この状態に気づかずに、うっかりコミット」した想定にしておきます。
差分が発生していると何が起きるか
今回はテストコードを使って、何が起こるかを見ていきます。
「第3のブランチ(feature/add_column
)ではまだUNIQUE制約を付けるマイグレーションがないから、テストはパスするだろう」と考え、以下のテストコードを書いたとします。
しかし、RSpecを実行すると
expected no Exception, got #<ActiveRecord::RecordNotUnique: SQLite3::ConstraintException: UNIQUE constraint failed: books.isbn> with backtrace:
とテストが失敗してしまいます。
これは、Railsガイドで解説があるように、テストでは schema.rb
を見てテスト用のデータベースを作っているためです。
今回の場合、UNIQUE制約を追加するマイグレーションファイルが存在しない第3のブランチ(feature/add_column
)でも、UNIQUE制約付きでテスト用のデータベースが作成されてしまった結果、テストが失敗しました。
どう復旧するか
今回は、以下の流れで復旧していきます。
UNIQUE制約を追加した第2のブランチ(
feature/add_unique_index
)へ切り替え、UNIQUE制約のマイグレーション適用をロールバックする第3のブランチ(
feature/add_column
)へ切り替え、db:migrate
を実行する
順に見ていきます。
1. UNIQUE制約を追加した第2のブランチ(feature/add_unique_index
)へ切り替え、UNIQUE制約のマイグレーション適用をロールバックする
feature/add_unique_index
)へ切り替え、UNIQUE制約のマイグレーション適用をロールバックするUNIQUE制約を追加した第2のブランチ( feature/add_unique_index
)へ切り替え、マイグレーションの状況を確認します。
Migration IDに対するNameのうち、NO FILE
と表示されていたものが Add index to book
という表示へと切り替わりました。
続いて、第2のブランチ( feature/add_unique_index
)上で、不要なマイグレーション適用を元に戻します。
なお、 db:migrate:rollback
コマンドでは1ステップずつしか戻せないので、バージョンを指定できるコマンドで戻します。
再度マイグレーションの状況を確認すると、当該Migration IDが down
という未適用状態になりました。
ただ、この時点では schema.rb
に変更が入っていることから、第3のブランチ(feature/add_column
)へ切り替えようとしても
と、エラーになってしまいます。
そこで、 schema.rb
の変更を、当ブランチのコミット時点に戻しておきます。
2. 第3のブランチ(feature/add_column
)へ切り替え、 db:migrate
を実行する
feature/add_column
)へ切り替え、 db:migrate
を実行するあらためて第3のブランチ(feature/add_column
)へ切り替えます。
ここでマイグレーション適用状況を確認すると、第3のブランチ(feature/add_column
)のマイグレーションのみが適用・表示されています。
ただ、まだ schema.rb
にはUNIQUE制約の定義が残ったままになっています。
そこで、もう一回 db:migrate
を実行し、 schema.rb
を更新します。
ターミナルの実行結果には何も表示されないものの、git status
ではschema.rb
の更新が確認できます。
更新後の schema.rb
を見ると、UNIQUE制約の定義が削除されています。
これでようやく schema.rb
が復旧しました。
あとは忘れずに schema.rb
を第3のブランチ(feature/add_column
)へコミットすれば、復旧は完了です。
おわりに
今回は schema.rb
に差分が発生する事例とその復旧方法を見てきました。
復旧するのには手間がかかることから、もしプルリクエストにマイグレーションファイルが含まれている場合は、schema.rb
に余分な更新がないかチェックするのが良さそうです。
本サイトの更新情報は、Twitterの株式会社プレセナ・ストラテジック・パートナーズエンジニア公式で発信しています。ご確認ください。
最終更新