svoop
svoop

Reputation: 3464

Prevent "initialization autoloaded the constant" for value objects in initializer

In a Rails initializer, I assign additional application configuration as follows:

module MyApp
  class Application

    config.x.email = {
      default: '[email protected]',
      invoice: '[email protected]'
    }

  end
end

Recently, however, I had to introduce a value object class for those email addresses to encapsulate some logic needed to properly align with SPF/DKIM/DMARC:

module MyApp
  class Application

    config.x.email = {
      default: EmailAddress.new('[email protected]'),
      invoice: EmailAddress.new('[email protected]')
    }

  end
end

On Rails 6.1, this triggers a warning:

DEPRECATION WARNING: Initialization autoloaded the constant EmailAddress.

Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.

Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload EmailAddress, for example,
the expected changes won’t be reflected in that stale Class object.

This autoloaded constant has been unloaded.

In order to autoload safely at boot time, please wrap your code in a reloader
callback this way:

    Rails.application.reloader.to_prepare do
      # Autoload classes and modules needed at boot time here.
    end

That block runs when the application boots, and every time there is a reload.
For historical reasons, it may run twice, so it has to be idempotent.

Check the "Autoloading and Reloading Constants" guide to learn more about how
Rails autoloads and reloads.

I'm not sure how to follow this advice in this particular spot.

As a workaround, adding require "email_address" to the top of the initializer gets rid of the warning, but it's not really solving the issue at its core.

Any ideas how to tackle this?

Thanks for your help!

Upvotes: 1

Views: 403

Answers (1)

Alex
Alex

Reputation: 30036

You have to wrap the whole config part in to_prepare block:

# config/initializers/email_config.rb

Rails.application.reloader.to_prepare do
  Rails.application.config.x.email = {
    default: EmailAddress.new("[email protected]"),
    invoice: EmailAddress.new("[email protected]")
  }
end

You can also put your email_address.rb in lib directory and just require it, once you figure out all the behaivor for EmailAddress you won't really need it to be reloadable. Restart will be required if you make any changes to EmailAddress in this case:

# config/initializers/email_config.rb

require "email_address" # => loads lib/email_address.rb

Rails.application.config.x.email = {
  default: EmailAddress.new("[email protected]"),
  invoice: EmailAddress.new("[email protected]")
}

Another option is to have email_address.rb in autoload_once_paths, which is the same as requiring it but without the require:

# config/application.rb

config.autoload_once_paths << Rails.root.join("app/value_objects")
# config/initializers/email_config.rb

# EmailAddress will be loaded from app/value_objects/email_address.rb only once

Rails.application.config.x.email = {
  default: EmailAddress.new("[email protected]"),
  invoice: EmailAddress.new("[email protected]")
}

https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoloading-when-the-application-boots

Upvotes: 2

Related Questions