さいとブログ

Railsのmigrationとschema_versionsの関係について

Railsのmigrationのバージョン管理がどうやって行われていて、エラーが発生したときにどう対処すればいいか、調べてみたのでまとめていきます。

すでにRailsに関する記事はweb上に山程あって、この記事に目新しい内容は書かれないですが個人の学習記録として残してます。
新しい技術をちゃんとキャッチアップするには、アウトプットが重要なので。

環境

ruby 3.0.6
rails 7.0.6
postgres 15


バージョン管理について

railsのmigrationファイルは、/app/db/migrate/に<日時create_model.rb>のような形式でファイルが作られます。
bin/rails db:migrate を実行したときに、どこまでマイグレーションが実行されているかの管理をデータベースのschema_migrationsのほうで記録して、管理しています。
ですので、schema_migrationsに記録されているバージョンと db/migrate/ に存在するmigrationファイルの整合性が合わなくなると、マイグレーションコマンドを実行するときに予期せぬエラーが発生します。

試してみる

以下のコマンドでモデル(必要ないけど)とマイグレーションファイルを作ります。

bin/rails g model user


そうすると、 20230806103311_create_users.rbが生成されます。
この状態で実行すると、dbのschema_versionsに以下のようにバージョンが記録されます。

    version
----------------
 20230806103311


この状態でマイグレーションファイルを削除するなりして、 bin/rails db:rollback を実行すると、

rails aborted!
ActiveRecord::UnknownMigrationVersionError:

No migration with version number 20230806103311.

Tasks: TOP => db:rollback
(See full trace by running task with --trace)


マイグレーションバージョンに記録されたファイルがありませんとエラーが吐かれます。
これは、schema_versionsに記録されたバージョンのマイグレーションファイルがrailsアプリケーションのディレクトリに存在しないため発生します。

わざと、マイグレーションファイルを削除することはないと思いますが、マイグレーション実行して、そのマイグレーションファイルをgit stashした時でも発生するので気をつけましょう。

次は、削除したマイグレーションファイルを復活させて、dbのschema_versionsに記録されたバージョンを削除してみます。
普通はやらないと思うので、あくまでも実験です。

delete from schema_migrations where version='20230806103311';
DELETE 1


そうすると、schema_versionsからは20230806103311のマイグレーションが実行された記録が消えます。
この状態でrollbackやmigrateすると、どうなるでしょう。

bin/rails db:rollback


すると、特にコンソールにログは出力されないです。
じゃあ、usersテーブルは削除されたのか、dbを覗いてみます。

default=# \dt;
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | ar_internal_metadata | table | postgres
 public | schema_migrations    | table | postgres
 public | users                | table | postgres
(3 rows)


usersは残っているのでrollbackは正常終了したように見えて、実行されていないのが見て取れます。

では、次にmigrateします。

bin/rails db:migrate

== 20230806103311 CreateUsers: migrating ======================================
-- create_table(:users)
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::DuplicateTable: ERROR:  relation "users" already exists
プロジェクト名/db/migrate/20230806103311_create_users.rb:3:in `change'

Caused by:
ActiveRecord::StatementInvalid: PG::DuplicateTable: ERROR:  relation "users" already exists
プロジェクト名/db/migrate/20230806103311_create_users.rb:3:in `change'

Caused by:
PG::DuplicateTable: ERROR:  relation "users" already exists
プロジェクト名/db/migrate/20230806103311_create_users.rb:3:in `change'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)


すでにusersテーブルはありますと、エラーが出力されます。
この挙動を見る限り、マイグレーションは実際にテーブル名を見ているのではなく、schema_versionsの中身を見て、どこまで実行されているかを判別しているように見えます。

試しにmigrate statusすると、

>>> bin/rails db:migrate:status

database: default

 Status   Migration ID    Migration Name
--------------------------------------------------
  down    20230806103311  Create users


usersテーブルはあるのに、ステータスではdownになっています。

ここらへんの挙動を色々実験した結果、エラー発生したときにどこを見て、どう対処したらいいか理解が深まりました。

プロフィール

profile icon

saitoです。
ソフトウェアエンジニアとして働いています。
web開発に関する学びを当ブログに書き残しています。