Devise: Rails6 without ActionMailer won't boot with zeitwerk eager-loading

Created on 24 Sep 2019  路  7Comments  路  Source: heartcombo/devise

Environment

  • Ruby 2.6.3
  • Rails 6.0.0
  • Devise 4.7.1

Current behavior

In a Rails 6 app without ActionMailer included (rails new testapp --skip-action-mailer), the default zeitwerk loader errors when eager-loading due to mailers/devise/mailer.rb not defining Devise::Mailer. Output from rails zeitwerk:check:
expected file /usr/local/bundle/gems/devise-4.7.1/app/mailers/devise/mailer.rb to define constant Devise::Mailer,

Expected behavior

Zeitwerk eager-loading should work without ActionMailer. Suggested options:

  • (easy) Define a dummy Devise::Mailer when ActionMailer is not defined.
  • (hard?) Customize the zeitwerk loader for when ActionMailer is not used.
Bug Needs PR

Most helpful comment

In case others come here in search for a quick solution:

It's possibly to work around the issue by simply adding ActionMailer in config/application.rb along with the other frameworks:

require "action_mailer/railtie"

All 7 comments

Idiomatically, you want to ignore that file. It could done with something like this in Devise (off the top of my head):

initializer "devise.configure_zeitwerk_if_enabled" do
  if Rails.autoloaders.zeitwerk_enabled? && !defined?(ActionMailer)
    Rails.autoloaders.main.ignore("#{__dir__}/relative/path/to/devise/mailer.rb")
  end
end

@fxn I would only add a check for Rails::Version::MAJOR >= "6" so it does not break on older versions of Rails where the method zeitwerk_enabled? does not exist, wdyt?

Oh yes, totally.

In case others come here in search for a quick solution:

It's possibly to work around the issue by simply adding ActionMailer in config/application.rb along with the other frameworks:

require "action_mailer/railtie"

For what it's worth, in a Rails 6.0.1 app without the ActionMailer railtie, it seems that defining Devise::Mailer as an empty class does not break anything. The app sends emails via a 3rd party service.

# <path-to-gems>/devise-4.7.1/app/mailers/devise/mailer.rb

if defined?(ActionMailer)
  class DeviseMailer < Devise.parent_mailer.constantize
    # whatever was already there
  end
else
  class DeviseMailer
  end
end

Tested authentication, registration, validation, forgot password flows.

Obviously inferior solution to explicitly ignoring the file in an initializer, but thought I'd share.

Hi everyone, I'm working on this and I was wondering how can I write a test to check whether the file has been ignored or not please as I don't have an in-depth Rails API knowledge ?

This is what I added so far (else statement) :

if defined?(ActionMailer)
  class Devise::Mailer < Devise.parent_mailer.constantize
    # whatever was already there
else
  if Rails::Version::MAJOR >= "6"
    Rails.autoloaders.main.ignore("#{__dir__}/relative/path/to/devise/mailer.rb")
  end
end

Thank you for your help.

Out of curiosity, why _isn't_ the preferred solution here simply for Devise to start using "zeitwerk-correct" file structure and class naming?

Was this page helpful?
0 / 5 - 0 ratings