はじめに
Rails でアプリケーションを作成中、あるgemのコマンドを実行するとZeitwerk::NameErrorが発生しました。
expected file /Users/suzuka/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/devise-4.9.3/app/mailers/devise/mailer.rb to define constant Devise::Mailer, but didn't (Zeitwerk::NameError)
調べたことの理解を深めるため、解決した手順をまとめます。
実行環境
エラーの原因
アプリケーションを作るときに、メール機能を使う予定がないため、Action Mailer をスキップしたことです。
rails new sample_app --skip-action-mailer
調べたこと
Action Mailerが無効な場合、Devise::Mailerクラスは定義されない
エラー文にある devise/mailer.rbのファイルを見に行ったところ、以下のようなコードが書かれていました。
deviseはActionMailer
が定義されている場合のみ、Devise::Mailer
クラスを定義しています。
if defined?(ActionMailer) class Devise::Mailer < Devise.parent_mailer.constantize include Devise::Mailers::Helpers ...... end end
このアプリではAction Mailerをスキップしているので、Devise::Mailer
クラスは定義されません。
Zeitwerkはファイル名からクラス名を推測する
エラー文にある、Zeitwerkとは何なのでしょうか?
Rails6から導入された、命名規則に則ったファイルを自動で読み込むgemで、「ツァイトヴェルク」と読むようです。このgemがあることによって、require
をたくさん書かなくても、必要なファイルを読み込めるようになります。
github.com
Rails6以降、デフォルトで読み込みはzeitwerk
モードで実行され、ファイル名と異なる命名規則のモジュール名・クラス名が定義されていた場合はエラーが発生します。
Zeitwerk使用時の、ファイル構造と定義されているクラスとモジュールの例は、以下のとおりです。
lib/my_gem.rb -> MyGem
lib/my_gem/foo.rb -> MyGem::Foo
lib/my_gem/bar_baz.rb -> MyGem::BarBaz
lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
つまり、devise/mailer.rb
という名前のファイルがあることで、Zeitwerkは「Devise::Mailer
クラスが定義されているはず」と判断します。しかし、このアプリでは実際にはDevise::Mailer
クラスが存在しないため、Zeitwerk::NameError
が発生しました。
解決方法
以下のIssueを参考にしました。
このIssueでは、2 つの解決方法をあげていますが、今回はより簡単な「ダミーのDevise::Mailer
クラスを定義する」という方法で行うことにします。
config/initializers/devise.rb
に以下を追記しました。
if Rails.autoloaders.zeitwerk_enabled? && !defined?(ActionMailer) class Devise::Mailer; end end
このコードによって Zeitwerkがファイル名から予測するクラスを定義することができ、エラーを解消することができました。
参考文献
Zeitwerkについて:
Zeitwerk::NameErrorの解決について: