Railsアップグレード時に bin/rails app:update
したときに生成されるconfig/initializers/new_framework_xxx.rb
が効かなかったときのハマりメモ。Railsのバージョンは5.1.4です。
原因と対応方法に関しては以下の記事がとても詳しいです。
本記事ではもう少し堀下げて解説していきます。Railsの初期化ですがだいたいこんな感じで行われます。
- config/environment.rb
- config/application.rb
- config/boot.rb
- Bundler.require
- Gemfileのgemをrequire
- XXX::Application
- Rails.application.initialize!
- イニシャライザの呼び出し
- config/application.rb
代表的なRailsのコンポーネントと設定ファイルだと以下の順番でRailtieのinitializerや設定ファイルの中身が読み込まれます。
- config/environments/xxx.rb
- action_controller
- active_record
- config/initializers/xxx.rb
- action_view
例えばActiveRecordだとこんな感じ
module ActiveRecord
# = Active Record Railtie
class Railtie < Rails::Railtie # :nodoc:
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
app.config.active_record.each do |k, v|
send "#{k}=", v
end
end
end
ActiveSupport.on_load(:active_record)
のブロックが実行される際のコンテキストはActiveRecord::BaseなのでActiveRecord::Baseのクラスメソッドのセッターメソッドによってconfigの値がコピーされています。
module ActiveRecord
class Base
# ...
ActiveSupport.run_load_hooks(:active_record, Base)
end
つまりActiveRecord::Railtieのinitializerが起動してActiveRecord::Baseが一度読み込まれてしまうと、configの値を変更してもActiveRecord::Baseの変数を変えることにはならないので設定が効かない、ということになります。
すべてのgemがActiveRecord::Baseを直接利用しないように作られているのであれば良いのですが、たとえばActiveRecord::Base.send :include, XXX
というように ActiveSupport.on_load(:active_record)
のフックを利用しないで拡張するgemがある場合は、gemを読み込んだ時点でオートロードによってActiveRecord::Baseが読みこまれてしまいます。
結果としてActiveRecord::Railtieのinitializerを実行した瞬間に設定値がセットされ、後続のconfig/initializers/xxx.rb等でのRails.application.config経由のActiveRecord::Baseへの設定ができなくなります(ただし、ActiveRecord::Base直接変更することはできます)
この問題の嫌なところはgemに依存するという部分で、プロジェクト単位でも利用するgemが変わってくる上に、同一プロジェクトでもRailsの環境(development, test, production)で読み込まれるgemが変わってくるのでそれぞれの環境で挙動が変わってきます。実際、developmentのgemに問題が有って、CIはちゃんと動くのに開発環境ではエラーが多発…ということがありました。
解決策
上の記事でも書かれておりますが、gemを直していくのが根本的な解決です。が、config/application.rbにRails関連の設定を全て入れちゃうのも手です。initializerよりも確実に早く実行されるので設定が効くようになります。