migration時にreset_column_informationを実行するとdb:seedしたときにNoMethodErrorが発生する

## TL;DR:

`RAILS_ENV=test rails db:migrate:reset db:seed`実行したときに`reset_column_information`があると意図しないDBスキーマがRailsによってキャッシュがされてしまい、`NoMethodError`が発生した。

## 現象:

`RAILS_ENV=test rails db:migrate:reset`自体は成功しており、DBには期待したテーブル構成が構築されていることを確認できた。
ところがそのあとに実行される`db:seed`が期待した挙動にならずエラーが発生する。

`RAILS_ENV=test rails db:migrate:reset`と`RAILS_ENV=test rails db:seed`を別々に実行すると成功するという謎挙動に遭遇した。

## 原因:

まだ具体的にどこのコードが影響してそうなったのか？がわかっていないのだが少なくとも原因の主な理由は `reset_column_information` にあることが判明した。

発生する条件としては↓のような状態となる。

- `rails db:migrate:reset db:seed`をワンライナーで実行する
- マイグレーションファイル内で`reset_column_information`を実行している
- `RAILS_ENV`が`test`
  - `development`だと発生しない、理由は後述する

## 再現:

↓な感じのマイグレーションファイルを用意する。

```ruby

class RenameCancelledToStatus < ActiveRecord::Migration[5.1]
  def up
    rename_column :model, :cancelled, :ok

    Model.reset_column_information
    Model.find_each do |model|
      model.ok = !model.ok
      model.save!
    end
  end
  
  def down
    rename_column :model, :ok, :cancelled

    Model.reset_column_information
    Model.find_each do |model|
      model.cancelled = !model.cancelled
      model.save!
    end
  end
end
```

内容としてはイケてないカラム`cancelled`さんを`ok`さんにリネームするというもの。
(カラム名に対して突っ込んではいけない、わかりやすくするためにあえて名付けているのだ。)

`Model`のカラムをリネームしているのでそのままだとリネーム前のカラムを参照してしまうので`reset_column_information`でモデルデータを読み込み直している。

一見？問題なさそうに見える。
実際`RAILS_ENV=development rails db:migrate:reset db:seed`したところ、正常に完了する。
DBに入っている値もきちんと反転した状態で入っていたので意図した値が登録されているため、問題ないと思っていた、このときまでは。

### CIサーバーからFailの通知が来た

一応手元の環境で確認が出来たので`git push`をしてCIを回したところテストがFailしたという通知が届いた。
CIをチェックするとDBの初期化、つまり`RAILS_ENV=test rails db:migrate:reset db:seed`で`NoMethodError`が発生して失敗をしている。

何故 `db:seed` でエラーになる？とこの時点でかなり頭を抱えそうだなと感じていたがだいたいこの手の嫌な予感は外れない法則が的中した。

エラーメッセージをみると `NoMethodError: undefined method 'ok='` が発生しており、現象としては`db:seed`を実行して`model.ok`というカラムにデータを突っ込もうとしたらエラーという状況になっているということまではわかった。
はて？migration時にはエラーにならないのに何故 `db:seed` でエラーになるのか？がよくわからなくて途方に暮れた。

ちなみに当初 `'ok='` というメソッドでエラーになってしまっているので何かしらのタイポをしたのだと思っていた。

## とりあえずいろいろ調査する

ちょっと原因の想定がつかないな…と思いながらもまずは手元の環境でログを出して調べようと`rails db:migrate:reset db:seed`を実行する→何故か完了するという現象に遭遇してしまう。
(このとき手元の環境は`RAILS_ENV=development`だった)

この時点で完全に謎の迷宮に放り込まれたが、StackOverflowやGithubのissueなどをみたり、いろいろ手元で試した結果「これはそもそもRailsがModelを起動時にキャッシュしているのでは？」という仮説が浮かび上がった。

<img width="390" alt="スクリーンショット 2018-07-20 17.58.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/18493/0d442916-5754-2990-62a9-77196ff2359a.png">

その後同僚が協力して調べてくれた内容を共有してくれて恐らくキャッシュのせいだろうと結論づけた。

<img width="649" alt="スクリーンショット 2018-07-20 18.01.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/18493/e28c14bd-7ab7-9001-a43b-8a7c6f6db8ce.png">

## 解決方法:

Railsのキャッシュ周りがどうなっているのか？を調べる時間的余裕がこのときあまりなかった。
そのうえでどう解決するかを考えたところ、そもそも `RAILS_ENV=test rails db:migrate:reset db:seed` を行うときにだけ発生するのでとりあえずCIでワンライナーで実行しているところを分割するという対策にしようということになった。

本来はきちんと解決すべき問題なのかもしれないがそもそもの影響範囲がテストのときだけ(開発環境は`RAILS_ENV=development`なので影響がない)ということで少なくともいまのところ問題にはなっていない。

## まとめ:

今回の想定外だった箇所としては2つあると考えている。

- `rails db:migrate:reset db:seed`が`rails db:migrate:reset && rails db:seed` と等価だと思っていたこと。
    - 実際にはキャッシュの生成タイミングなどに微妙な差異があった。
- `db:seed`実行時に最新のモデルが読み込まれていると思い込んでいたこと
    - ワンライナーで実行したときにも`db:migrate:reset`後にリロードされると思っていたが実際にはそうでなかった
        - 今にして思えばリロードしてしまうのはコストが高い気がするので仕方ないように思う。

ということで根本的な解決ではないが対処方法としてはまあまあ妥当でかつ簡単に対応出来た点は良かったかもしれない。

恐らくもっと正しい対処方法があると思うので知ってる方がいたら教えてください。