背景
Rails 8.0 + SQLite3 環境でチーム開発をしていたら、自分は何もマイグレーションを変更していないのに db/schema.rb の外部キーカラムが bigint から integer に変わる差分が出た
# before
t.bigint "user_id"
# after
t.integer "user_id"
「DBのデータ壊れた?」と焦ったので調べた
結論
DB上のデータは一切変わっていない。sqlite3 gem のバージョン違いによって schema.rb への翻訳ルールが異なるだけ
対処法は Gemfile で sqlite3 gem のバージョンを明示すること
# Gemfile
gem 'sqlite3', '~> 2.9'
そもそも schema.rb はどう生成されるのか
schema.rb は手で書くファイルではなく、マイグレーション実行時にローカル DB の状態を読み取って自動生成されるファイル
マイグレーション実行
→ ローカルSQLiteにカラム追加
→ sqlite3 gemがDBスキーマを読み取り
→ schema.rb に翻訳して書き出す ← ここで差が出る
つまり schema.rb の内容は「DB の実態」と「gem の翻訳ルール」の掛け算で決まる
なぜ bigint と integer の差分が出るのか
SQLite には BIGINT 型が存在しない。内部的には外部キーも含め全て INTEGER として保存される
問題は sqlite3 gem がこの INTEGER を schema.rb に書き出す際の翻訳ルールがバージョンによって違うこと
| sqlite3 gem のバージョン | 翻訳結果 |
|---|---|
| あるバージョン |
INTEGER → t.bigint "user_id"
|
| 別のバージョン |
INTEGER → t.integer "user_id"
|
DB上のデータは全く同じなのに、gem の翻訳ルールが違うため schema.rb のテキストだけが変わる
なぜチーム内で差が出るのか
Gemfile でバージョン指定なし(例: gem 'sqlite3')だと、Gemfile.lock で固定されていても、過去に別バージョンで生成された schema.rb との差分が残ることがある
チームメンバーが異なるタイミングで bundle install していると、ローカルにインストールされている gem のバージョンが微妙にずれる可能性がある
対処法
Gemfile で sqlite3 gem のバージョンを明示して、チーム全員の翻訳ルールを揃える
gem 'sqlite3', '~> 2.9'
また、schema.rb をコミットする際は 自分のマイグレーション分以外の差分が含まれていないか を必ず git diff db/schema.rb で確認する
感想
- 「DB のデータは同じなのに gem の翻訳ルールだけが変わる」という構造がわかると、もう怖くない
-
schema.rb関連の問題に遭遇するたびに思うけど、「自動生成ファイルの差分はまず生成元を疑え」は鉄則やな
参考
- Datatypes In SQLite Version 3 - SQLiteが BIGINT を INTEGER のエイリアスとして扱う仕様
- Rails Issue #43168 - Integer column will change to bigint type after running db:migrate - この問題についてのRails公式Issue
- Rails Issue #46834 - Migrating after a DB reset changes foreign keys from bigint to integer - DB リセット後に外部キーの型が変わる問題
- Rails Guide - Active Record Migrations - schema.rb の役割と生成の仕組み