はじめに
Railsのアップデート中にproduction環境でサーバーを起動する時にエラーが出たので、その対応をまとめます。
問題
エラー文
app/shared/bundle/ruby/3.2.0/gems/zeitwerk-2.6.14/lib/zeitwerk/loader/helpers.rb:139:in `const_get': uninitialized constant xxx (NameError)
parent.const_get(cname, false)
^^^^^^^^^^
または、
app/shared/bundle/ruby/3.2.0/gems/zeitwerk-2.6.14/lib/zeitwerk/loader/callbacks.rb:33:in `on_file_autoloaded': expected file /path/xxx.rb to define constant yyy, but didn't (Zeitwerk::NameError)
raise Zeitwerk::NameError.new(msg, cref.last)
解決方法
Rails6からオートローディングが、Zeitwerkモードによって行われるようになったことが原因です。
解消方法は、エラー文からどのファイル、定数でエラーが出ているか確認し、ファイル名、定数名が正しいものになっているか確認します。
まずは、Railsのオートローディングについてです。
オートローディング
クラスやモジュールを手動でrequireしなくても、必要なときに自動的に読み込んでくれる仕組み。
クラスやモジュールが必要になった時点で自動的にRailsがファイルを探してロードしてくれます。
環境での違い
- development環境
- 変更したファイルは再度ロードされ、最新の状態でアプリケーションが実行されます。
- 必要になった時に、クラスやモジュールを読み込みます。
- production環境
- 全てのクラスはアプリケーションの起動時にロードされ、一度ロードされたクラスは再読み込みされません。
- 最新のコードを反映するには、サーバーの再起動が必要です。
次に、Rails5以前のオートローダーとRails6以降のオートローダーの違いです。
オートローダー
Zeitwerkモード(Rails6以降)
- ファイル名から定数名を推測する(ファイル名:スネークケース、クラス名:パスカルケース)
- app/models/user_content.rbにはUserContentクラスが定義されている必要がある
- app/models/admin/user.rbにはAdmin::Userが定義されている必要がある
- コードの読み込みが安定、高速
- スレッドセーフ
Classicモード(Rails5以前)
- ファイル名と定数名が一致していなくても動く
- app/models/user.rbにmyUserクラスが定義されていても大丈夫
- 複雑な名前空間を使用した際には、ファイルの配置や読み込み順序で問題が発生することがある
- スレッドセーフではない
これを元に、ファイル名とクラス名がZeitwerkモードのルールに則っているかを確認してください。
オートローディングのdevelopment環境とproduction環境の動作の違いから、development環境では、使用されるコードのみ読み込まれますが、production環境では使用の有無に関わらず、全てのファイルをサーバー起動時に読み込むので、production環境でのみエラーが出ることがあります。
また、Rails7.0まではZeitwerkモードのファイル読み込みの対象は、appディレクトリ配下のみでしたが、Rails7.1からはlibディレクトリ配下もファイル読み込みの対象となっています。
参考