すずかのプログラミング勉強記

元教員からエンジニアを目指す、プログラミング勉強記録です。

Action Mailerのスキップ時に発生するZeitwerk::NameErrorの解決方法

はじめに

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を参考にしました。

Rails6 without ActionMailer won't boot with zeitwerk eager-loading · Issue #5140 · heartcombo/devise · GitHub

このIssueでは、2 つの解決方法をあげていますが、今回はより簡単な「ダミーのDevise::Mailerクラスを定義する」という方法で行うことにします。

config/initializers/devise.rbに以下を追記しました。

if Rails.autoloaders.zeitwerk_enabled? && !defined?(ActionMailer)
  class Devise::Mailer; end
end

このコードによって Zeitwerkがファイル名から予測するクラスを定義することができ、エラーを解消することができました。

参考文献

Zeitwerkについて:

Zeitwerk::NameErrorの解決について: