Ruby
Rails
SQLite3

rails db:migrate:resetできなかったのでrails db:resetした

結論から

dbをmigrateするときにエラーが出た場合、rails db:migrate:resetでDBリセットとmigrateを同時に行おうとすると失敗する場合がある。
migrationファイル内の実行順に問題がある可能性があるので、rails db:resetrails 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:resetrails 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を実行しても復旧できないことがあります。スキーマダンプの詳細については、スキーマダンプの意義 セクションを参照してください。

4.3 データベースをリセットする

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ファイルを確認して不整合を修正する必要があります。