Rails
Rails5

autoload_pathsを修正して謎エラーをなくした話

アドベントカレンダー15日目になります!


config.autoload_pathsの設定

最近、現場でautoload_pathsを修正する機会がありました。

#config/application.rb

config.enable_dependency_loading = true # ウッ!

config.autoload_paths += %W(#{config.root}/lib) # 重複・・?
config.autoload_paths += Dir["#{config.root}/lib/**/"]
config.autoload_paths += %W(#{config.root}/app/services)

# ...snip
#
# 以下app/以下のautoload設定が並ぶ
#

歴史的な何かで、本番でのautoloadが可能になっていたのですが、やっかいなエラーを引き起こすのでオフにします。

ついでに他の設定も見直してみました。


現状

どうやらapp/以下に切った独自のフォルダに加えて、lib/以下をautoload_pathに加えたいという意図がありそうです。

さらにautoloadをオフにするにあたって、現在autoload対象になっているフォルダをすべて本番でeager_load対象にする必要がありました。


やったこと

ロードしたい対象フォルダを、本番用にeager_load指定しつつ、開発環境用に別途autoload指定もしないといけないかと思っていましたが、この辺をみるとeager_load_pathsに指定するとautoload_pathsにも含めてくれるようです。

また、app/以下の独自フォルダですが、デフォルトでeager_load_pathsに加えられます

つまりapp/以下のファイルのロードについて心配する必要はありません。

なので、残るはlib/以下の対処だけとなり、最終的にこんなシンプルになりました。

config.eager_load_paths += Dir["#{config.root}/lib/**/"]

lib/tasks以下にあるrakeタスクがeager_loadされているのが気持ち悪いので、のちほど手を打とうかなと考えています。

 config.autoload_paths += Dir["#{config.root}lib/**/"] -  Dir["#{config.root}lib/tasks/**/"]

うん。ダサいw

どうやらpathオブジェクトを使ってフォルダ丸ごと除外するというコードが最近railsに入ったっぽい。

いろいろと試してみて、いい感じにかけるようであればまたシェアしたいと思います。


検証方法

念のためステージングでeager_loadされていることを確認しました。

Rails.configuration.eager_load_paths

=> [.... # eager_loadされたパスが大量に並ぶ ]

ActiveSupport::Dependencies.autoloaded_constants
=> [.... # ロード済みの定数が並ぶ] # 起動直後でもlib/以下の定数が問題なく並ぶことを検証

一方、改修前のブランチ、およびローカル環境ではlib/以下の定数がロードされていないことを確認できました。

ActiveSupport::Dependencies.autoloaded_constants

=> [.... # 起動時にロードされたクラス名だけが並ぶ]


結果

なぜか本番デプロイの直後だけ、クラス名を参照したコードが落ちるという現象がありました。

Circular dependency detected while autoloading constant XXXXXX(クラス名)

エラーが起こるクラス名はその時々で変わるのですが、lib以下のクラス名についてだけこの現象が起こるということに気づき、lib以下のクラスがうまくロードされていない可能性を疑いました。

改修後1ヶ月ほど経ちますが、全くエラーが起こらなくなりました。


注意点

Rails Guideにautoload_pathsの設定に関して詳しい解説があります。

ちょっと気になる注釈があり、


But using autoload_paths on its own in the past (before Rails 5) developers might configure autoload_paths to add in extra locations (e.g. lib which used to be an autoload path list years ago, but no longer is). However this is now discouraged for most purposes, as it is likely to lead to production-only errors. It is possible to add new locations to both config.eager_load_paths and config.autoload_paths but use at your own risk.


「Rails5より前ではlib/以下をautoload_pathsに含めることがよく行われていたが、現在はappフォルダ以外の場所にコードを置いてautoload対象にするのは勧めない」となっています。本番特有のエラーが起こる可能性があるから、やるなら自己責任でな、という話です。

libを切っているアプリケーションはちょいちょい見かける印象ですが、どこかでフォルダ構成を見直した方がいいのかもしれませんね。