結論から
dbをmigrateするときにエラーが出た場合、rails db:migrate:reset
でDBリセットとmigrateを同時に行おうとすると失敗する場合がある。
migrationファイル内の実行順に問題がある可能性があるので、rails db:reset
とrails db:migrate
を分けて行うことで解決できる場合がある。
環境
Rails 5.1.2
Ruby 2.4.3
起こったこと
migrationでエラー
rails tutorialを進めていてdbをmigrateしようとしたらこんなエラーが出ました。
$ rails db:migrate
== 20171229141042 CreateUsers: migrating ======================================
-- add_index(:users, :email, {:unique=>true})
-> 0.0010s
-- create_table(:users)
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: table "users" already exists: CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "email" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL)
...
userテーブルが既にあるのにつくろうとしている、と怒られています。
reset:migrationでもエラー
そこで、 rails db:migrate:reset
しようとしました。
(このコマンドは7.4.3 実際のユーザー登録の章で触れられています)
$ rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20171229141042 CreateUsers: migrating ======================================
-- add_index(:users, :email, {:unique=>true})
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: no such table: main.users: CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email")
...
またusersテーブルがないと怒られました。
処理の流れを見てみると、DBを削除して作り直した後にindexを追加しようとしてこけています。そしてusersテーブルを作成する処理が行われていません。
migrationファイルを見てみる
migrationファイルを見てみると、原因がわかりました。
usersテーブルを作成する前にadd_indexしてしまっています。
rails tutorialを進める中で、migrationファイルを作る前にDBを色々操作したことが原因のようです。
class CreateUsers < ActiveRecord::Migration[5.1]
def change
add_index :users, :email, unique: true
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
こちらに書いてあることがヒントになりました。
https://stackoverflow.com/questions/33139275/error-on-migration-sqlite3sqlexception-no-such-table-main-users
上の記事のようにmigrationを修正しても良いのですが、公式では推奨しないと書いてあるし、既にあちこちデータを触ってしまっていたので、わたしは下記の手順でエラーを解消しました。
rails:db:resetしてみる
$ rails:db:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
-- create_table("users", {:force=>:cascade})
-> 0.0036s
-- create_table("users", {:force=>:cascade})
-> 0.0021s
DBが削除、再作成され、usersテーブルが無事作られました。
このあとmigrationすると、usersテーブルが存在しているため問題なく成功します。
rails db:resetとrails db:migration:resetの違い
rails db:reset
とrails db:migration:reset
はどちらも一旦全てのDBを 削除した後に作成し直すコマンドですが、下記の違いがあります。
rails db:resetについて
rails db:reset
はdb/schema.rbを元にDB作成します。db/migrate/**.rb は使われません。
Rails Guidesに下記のように記述があります。
bin/rails db:resetタスクは、データベースをdropして再度設定します。このタスクは、bin/rails db:drop db:setupと同等です。
このタスクは、すべてのマイグレーションを実行することと等価ではありません。このタスクでは現在のschema.rbの内容をそのまま使い回しているためです。マイグレーションをロールバックできなくなった場合には、bin/rails db:resetを実行しても復旧できないことがあります。スキーマダンプの詳細については、スキーマダンプの意義 セクションを参照してください。
rails db:drop db:setupと同義ということでrails db:setup
コマンドのドキュメント(4.2 データベースを設定する)を確認します。
- データベースの作成
- スキーマの読み込み
- シードデータを使用したデータベースの初期化
の3つの処理を実行すると書いてあります。
rails db:migrate:resetについて
一方rails db:migrate:reset
は、DBを削除した後に、db/migrate/**.rb を古い順から実行するそうです。
こちらは、migrateファイルを修正したときに、一度DBを削除した上で新たにmigrationを適用したい場合などに使うそうです。
ちなみにこちらは最新のRails Guidesにはこのコマンドが載っておらずRails 4系のドキュメントにしか記載がありませんでした。
rails -Tで検索しても存在しません。
英語版の5系のチュートリアルでもこのコマンドを実行する指示があるのですが、実際は裏コマンド扱いなのでしょうか...?
終わりに
resetとmigrateを分けてコマンドを実行する方法は、データをあちこち触る開発初期などに使うといいかと思います。
エラーの根本的解決のためには、DBとmigrationファイルを確認して不整合を修正する必要があります。