Rails 7からautoloadの仕組みがzeitwerkに完全移行したそうだ。
zeitwerkはファイル名から定数名を推測するという仕組みになっているらしい。例えば、自動ロードパスに置かれているhoge_fuga/piyo.rb
というファイルからはHogeFuga::Piyo
という名前の定数(モジュール・クラス)を読み込もうとする。もしHogeFuga::Piyo
が存在しないと以下のようなエラーになる。
$ bundle exec rails zeitwerk:check Hold on, I am eager loading the application. bin/rails aborted! NameError: uninitialized constant HogeFuga::Piyo
ここで注意すべきは HogeFuga::PIYO という定数があってもダメということだ。ActiveSupportのcamelizeでファイル名を定数名に変換しているため、全部大文字の定数を受け付けない。この場合、autoloaderの設定に単語を登録すると動作するようになる。詳細はRailsドキュメントを参照していただきたい
なんで7.0で問題なかったの?
ここで私が疑問に思ったのは、このエラーがRails 7.0では発生しなかったのに7.1アップデートで発生したこと。7.0の時点でzeitwerkが有効になっているにも関わらず、である
これは以下のような理由だった
- エラーが発生するファイルは lib/hoge_fuga/piyo.rb
- Rails 7.0まではlibディレクトリは自動ロードパスではない
- このため自動ロードではなくrequireで普通にロードしていた
- Rails 7.1でlibが自動ロードパスに含まれるようになった
- lib/hoge_fuga/piyo.rb が自動ロードの対象となった
HogeFuga::Piyo
が定義されていないためエラーが発生した
Rails 7.1でconfig/application.rbに config.autoload_lib(ignore: %w(assets tasks))
というコードが追加されており、これがlibを自動ロードパスに追加する設定である。rails app:update
でコンフリクトしたときにこの設定を外してマージしなければならなかったのだ
Awesome.
— Xavier Noria (@fxn) 2023年11月3日
Please note that lib is included only for new applications, the generator outputs an autoload_lib config line.
Upgraded don't need to add that line. You could leave the configuration as it was before.
Just to give you context/options. Happy that you solved it.
というわけで config.autoload_lib(ignore: %w(assets tasks))
をコメントアウトするのが1番簡単な解決法なのだが、せっかくなので全体をzeitwerkの流儀に合わせることにした
まず、config/initializers/zeitwerk.rbを作成し、以下のコードを追加
Rails.autoloaders.each do |autoloader| autoloader.inflector.inflect( 'fuga' => 'FUGA' ) end
requireしているコード(今回はlib/initializers/hoge.rb)を直す
- require 'hoge_fuga/piyo' - HogeFuga::PIYO.init(config: Rails.root.join('piyo_secret.json')) + Rails.configuration.after_initialize do + HogeFuga::PIYO.init(config: Rails.root.join('piyo_secret.json')) + end
zeitwerkの流儀に従うためrequireを取り除く。ただ、initializerが実行されている時点では自動ロードが完了していないようなのでafter_initialize
の中で定数にアクセスしなければならない