Skip to content

Rails edge: premature :action_mailer / :active_job load-hook warnings on every boot #925

@benaitcheson

Description

@benaitcheson

Summary

On Rails edge (the unreleased rails/rails main branch), every boot of an app using devise_invitable emits two WARN -- : ... was loaded before application initialization log blocks:

  • :action_mailer was loaded before application initialization
  • :active_job was loaded before application initialization

These come from Rails' new guard_load_hooks support (rails/rails#56201), which logs when a framework load hook fires before the app has finished initializing.

Root cause

The Engine defined in lib/devise_invitable/rails.rb uses config.to_prepare to include DeviseInvitable::Mailer into Devise.mailer:

config.to_prepare do
  Devise.mailer.send :include, DeviseInvitable::Mailer
  unless Devise.mailer.ancestors.include?(Devise::Mailers::Helpers)
    Devise.mailer.send :include, Devise::Mailers::Helpers
  end
end

config.to_prepare blocks fire during the Rails finisher initializer (before Rails.application.initialized? flips to true). Inside the block, Devise.mailer does a String#constantize on the configured mailer class name (default "Devise::Mailer"), which loads the mailer class. The mailer class extends ActionMailer::Base, and ActionMailer::Base's class body includes ActionMailer::QueuedDelivery, which requires action_mailer/mail_delivery_job, which extends ActiveJob::Base. Result: both :action_mailer and :active_job run_load_hooks fire during boot.

Example boot log

WARN -- : :action_mailer was loaded before application initialization.
Prematurely executing load hooks will slow down your boot time
and could cause conflicts with the load order of your application.
Please wrap your code with an on_load hook:

  ActiveSupport.on_load(:action_mailer) do
    # your code here
  end

Called from:
...
/devise_invitable-2.0.9/lib/devise_invitable/rails.rb:12:in 'block in <class:Engine>'
...

(Same shape for :active_job, via ActionMailer::BaseMailDeliveryJob.)

Proposed fix

Wrap the body of the to_prepare block in ActiveSupport.on_load(:action_mailer):

config.to_prepare do
  ActiveSupport.on_load(:action_mailer) do
    Devise.mailer.send :include, DeviseInvitable::Mailer
    unless Devise.mailer.ancestors.include?(Devise::Mailers::Helpers)
      Devise.mailer.send :include, Devise::Mailers::Helpers
    end
  end
end

The reload semantics noted in the existing comment are preserved: on every to_prepare cycle a fresh on_load(:action_mailer) block is registered, and since :action_mailer has fired by then on subsequent reloads it runs immediately. The first boot defers it until ActionMailer is actually loaded — past app initialization, so no warning.

I've opened a PR with this change against master: TBD-link.

Versions

  • Reproduces on devise_invitable v2.0.12 (latest as of writing).
  • Rails: the rails/rails main branch any time after rails/rails#56201 merged.
  • Stable Rails users won't see the warnings until Rails 8.2 (or whichever stable release ships guard_load_hooks).

Happy to add a regression test if helpful — wasn't sure how the existing test harness exercises railtie load order, so kept the PR minimal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions