2018-11-16

config/initializers/new_framework_xxx.rbが効かなかったときのハマりメモ

Railsアップグレード時に bin/rails app:updateしたときに生成されるconfig/initializers/new_framework_xxx.rbが効かなかったときのハマりメモ。Railsのバージョンは5.1.4です。

原因と対応方法に関しては以下の記事がとても詳しいです。

本記事ではもう少し堀下げて解説していきます。

Railsの初期化ですがだいたいこんな感じで行われます。

イニシャライザの呼び出しはRailtieやconfig/environments/xxx.rb、config/initializers/xxx.rb の呼び出しが含まれており、順番も決まっています。

代表的なRailsのコンポーネントと設定ファイルだと以下の順番でRailtieのinitializerや設定ファイルの中身が読み込まれます。

また、各コンポーネントのRailtieのinitializerではRails.application.configの設定値を各コンポーネントにコピーしています。

例えば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よりも確実に早く実行されるので設定が効くようになります。

このエントリーをはてなブックマークに追加