はじめに
Rails で以下のような migration を書いたのに、
add_index :users, :reset_password_token, unique: true
なぜか UNIQUE 制約が効かない、
rails db:migrate:reset をしても直らない……。
本記事は、実際に自分がハマった事例をもとに、
なぜ unique が反映されなかったのか、
なぜ rollback / reset でもダメだったのか
正しい解決方法は何かを 時系列で整理します。
環境は以下です。
Rails 7
Docker Compose
MySQL
Sorcery(reset_password_token)
発生した問題
以下の migration を作成しました。
class SorceryResetPassword < ActiveRecord::Migration[7.0]
def change
add_column :users, :reset_password_token, :string, default: nil
add_column :users, :reset_password_token_expires_at, :datetime
add_column :users, :reset_password_email_sent_at, :datetime
add_column :users, :access_count_to_reset_password_page, :integer, default: 0
add_index :users, :reset_password_token, unique: true
end
end
しかし MySQL で確認すると、
SHOW INDEX FROM users;
Non_unique | 1
UNIQUE になっていない
最初に試したこと(失敗)
① rollback して migration ファイルを書き換える
rails db:rollback
add_index :users, :reset_password_token, unique: true
rails db:migrate
反映されない
② migrate:redo を実行
rails db:migrate:redo VERSION=xxxxxxxxxxxxxx
ログを見ると、
-- add_index(:users, :reset_password_token)
unique: true が反映されていない
③ rails db:migrate:reset を実行
rails db:migrate:reset
それでも UNIQUE にならない
##なぜ反映されなかったのか(原因)
原因① migration は「設定」ではなく「履歴」
Rails の migration は
「今こうしたい」という設定
ではなく
「過去に何をしたか」という履歴
一度実行された migration を 書き換えるのは非推奨。
原因② index は上書きされない
すでに 非 UNIQUE の index が存在
同名 index がある場合、Rails は作り直さない
unique: true は後付けできない
原因③ Docker の MySQL volume
「rails db:migrate:reset」は
DB を drop / create するだけ
Docker の volume は削除されない
**Docker の volume は「コンテナを消しても残るデータ置き場」**です。
そのため、DB が完全初期化されていないケースがある
正しい解決方法(結論)
✅ 新しい migration を作成する(ベストプラクティス)
rails g migration FixUniqueIndexOnResetPasswordToken
class FixUniqueIndexOnResetPasswordToken < ActiveRecord::Migration[7.0]
def change
remove_index :users, :reset_password_token
add_index :users, :reset_password_token, unique: true
end
end
その後、
rails db:migrate
でマイグレーションを適用
確認
SHOW INDEX FROM users;
Non_unique | 0
UNIQUE 制約が正しく反映されました
まとめ(学び)
migration は 書き換えない
変更したい場合は 新しい migration で表現する
index の unique 変更は
👉 remove_index → add_index
Docker 環境では volume の存在を意識する
おわりに
「rollback したのに反映されない」
「migrate:reset したのに直らない」
という状態は、Rails が悪いのではなく
migration の思想を誤解しているだけでした。
同じところでハマる人の助けになれば幸いです。