Redmineプラグインのマイグレーションを適用した環境でdb:setup, db:resetを実行してはいけない

確認したバージョン: Redmine 4.0.4, 3.4.11（たぶん他のバージョンでも起きる）

1. Redmineプラグインのマイグレーションを適用済みの環境で
2. `rails db:setup`や`rails db:reset`を実行してしまうと
3. `rails redmine:plugins:migrate`を実行すると`Table already exists`といったエラーが出て失敗するようになる。[^rerunnable-migration]

[^rerunnable-migration]: プラグインのすべてのマイグレーションがロールバックせずに何度でも実行できるなら問題は起きないが、そういうケースはごくまれだと思う。

# 問題を起こさない方法

- 新しいDBにスキーマを適用したい場合には`rails db:migrate redmine:plugins:migrate`を実行する
- DBのスキーマを作り直したい場合には`rails db:migrate:reset redmine:plugins:migrate`を実行する[^protected-env-error]

[^protected-env-error]: `ActiveRecord::ProtectedEnvironmentError`が発生したら[Rails 5に入ったDB破壊系taskの防止処理について | 日々雑記](https://y-yagi.tumblr.com/post/142064773890/rails-5%E3%81%AB%E5%85%A5%E3%81%A3%E3%81%9Fdb%E7%A0%B4%E5%A3%8A%E7%B3%BBtask%E3%81%AE%E9%98%B2%E6%AD%A2%E5%87%A6%E7%90%86%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6 "Rails 5に入ったDB破壊系taskの防止処理について | 日々雑記")などを参照。

# 問題が起きたあとの修正方法

- `rails db:migrate:reset redmine:plugins:migrate`でDBを作り直す
- `schema_migrations`テーブルをいじってプラグインのマイグレーションが適用されたことにする
- プラグインのマイグレーションで追加されたテーブルなどを手動で消す

のどれかで直る。だいたい上のほうが簡単なはず。

# 原因

- `db:setup`や`db:reset`は`db/schema.rb`をソースにしてスキーマとマイグレーションの適用状況を復元する。
    - `db/schema.rb`のスキーマ情報にはRedmine本体のほか、プラグインのマイグレーションの結果も保存されているので、プラグインが追加したテーブルなどは正しく復元される。
    - しかしマイグレーションの適用状況はRedmine本体のものしか保存されていないので、プラグインのマイグレーションの適用情報は失われる。
- そのため、次に`redmine:plugins:migrate`を実行するとプラグインのマイグレーションが二重に適用されてしまう。
    - マイグレーションの中でテーブルやカラムを追加していた場合、同名のテーブルなどがすでに存在するため、エラーが起きてマイグレーションが失敗する。
- 一方、`db:migrate:reset`は`db:migrate`でマイグレーションをひとつずつ実行していくので、`schema_migrations`テーブルとスキーマの不整合が起きない。

## 用語メモ

- `db/schema.rb`: DBのスキーマ情報を保存したファイル[^structuresql]。
    - （↓以下2つは確認してないけどたぶん合ってるはず）
    - マイグレーションの実行が完了するたびに更新される。
    - 最後に実行が完了したマイグレーションのバージョンを保持している。
- `schema_migrations`テーブル: どのマイグレーションを適用したかを記録しておくためのテーブル。

[^structuresql]: 設定によっては`db/schema.rb`ではなく`db/structure.sql`になる。

## コードリーディングメモ

- [active_record/railties/databases.rake](https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/railties/databases.rake)
    - [`db:setup`の定義](https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/railties/databases.rake#L189)
    - [`db:reset`の定義](https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/railties/databases.rake#L154)
    - [`db:migrate:reset`の定義](https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/railties/databases.rake#L94)
    - `db:setup`, `db:reset`は[`db:schema:load`](https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/railties/databases.rake#L258)を呼び出し、そこから`DatabaseTasks.load_schema`が呼び出される。
- [active_record/tasks/database_tasks.rb](https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/tasks/database_tasks.rb#L245)
    - `load_schema`では`Kernel.load`で`db/schema.rb`を実行している
- [active_record/schema.rb](https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/schema.rb)
    - `db/schema.rb`で使われる`Schema`クラスの定義
    - `ActiveRecord::Schema.define`がそのままDBにスキーマを適用するメソッドとして定義されている
- [redmine/plugin.rb](https://github.com/redmine/redmine/blob/4.0.4/lib/redmine/plugin.rb#L560-L562)
    - `record_version_state_after_migrating`を上書きして、`schema_migrations`に保存するバージョン名の末尾にプラグインのIDを追加している。

# 再現コード

[再現確認用のリポジトリ](https://github.com/vzvu3k6k/demo-redmine_db_reset_with_plugin)を作った。pushするたびにGitHub Actionsで

1. 再現用のプラグインを用意する
2. Redmineのソースをダウンロードして展開する
3. プラグインと`database.yml`をRedmineのディレクトリに配置する
4. `rails db:create db:migrate redmine:plugins:migrate`を実行してから`rails db:reset redmine:plugins:migrate`を実行してエラーメッセージを確認する

という感じのことをやっていて、不具合が再現したらコミットのステータスが緑になる。