rails: Rails 6.1 Constant autoloading not working in environment configuration files
Steps to reproduce
Referencing an autoloaded constant in an environment configuration (e.g. config/environments/development.rb
) results in a NameError
(“uninitialized constant”). This worked correctly in Rails 6.0.3.4.
An example repo demonstrating the problem is available here: https://github.com/samstickland/rails-6.1-constant-resolution-issue . Running “rails c” is not possible in this repo with Rails 6.1. The first commit in the repo is the result of “rails new” and the second demonstrates the issue
Expected behavior
The constant should be resolved
Actual behavior
The constant is not resolved.
System configuration
Rails version: 6.1.0
Ruby version: 2.7.2
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 5
- Comments: 24 (18 by maintainers)
Thanks @fxn! Confirmed that worked, the require inside Application class body.
I don’t know why I didn’t try that first, I thought I did, but clearly not! It is a little bit more straightforward than the
config.before_configuration
rigamarole.Hopefully this will be findable and help others trying to move from using auto-loaded classes in configuration/initialization process. The little gotchas made it take more time than I thought it would.
@rromanchuk Let me add that I explained this to help understand the reason why this is the best option. However, I do not assume you had to know this, I believe this is not well documented. Maybe we need a clear contract of what can be done and what cannot be done at each stage of the boot process.
@rromanchuk Ah! In that case the proper solution is to move your code to
lib
and issue arequire
call.When the application boots,
config
is initialized. But the actual affected component does not operate withconfig
at runtime in general, and if it does, that is private implementation. Normally,config
is just a proxy that transports user configuration into internal component state. This is schematically what happens:Let’s imagine railtie
foo
providesFoo::Service
, which has a class attributeendpoint
. As a Ruby programmer developingfoo
you haveOK. In general, the internal way to represent things is not exposed, the public interface to it goes via
config
, an indirection, so that railtie has an initializer that does this:so to speak.
That is tied to the boot process, initializers run only once. If you change
config.foo.endpoint
on reload viato_prepare
, that does not run the initializer again. The endpoint value should not be reloadable, because it is used in a way that makes reloading pointless. See what I mean?So, better
require
fromlib
, so that it is clear thatSpecialJsonFormatter
is a class whose modification needs a server restart.@jrochkind before the line with the
class
keyword, the boot process is mostly loading dependencies. WhenRails::Application
has been subclassed, you havelib
. That means you can simply writeAlternatively, you can also use
require_relative
, in which case you do not depend on$LOAD_PATH
, and can be put at the top:but it is, perhaps, uglier.
The warning shipped with 6.0.0.
In 6.1 you cannot autoload in
config/environments/*.rb
, that is the first change. You still can autoload inconfig/initializers/*.rb
to ease the transition, but you should not, and it will err in Rails 7 (at least, that is the plan).We can close this one.
Yes. The reason for this is explained in the warning message.
MyNamespace::MyClass
is reloadable because it’s inapp/lib
. Whatever you do inconfig/environments/development.rb
withMyNamespace::MyClass
is not going to be reflected if you edit its implementation, because those initialization files do not run again.Options:
lib
and issue arequire
call inconfig/environments/*.rb
. If you change its code, a server restart is needed.to_prepare
block, or upgrade to Zeitwerk 2.4.2 and set anon_load
callback onRails.autoloaders.main
.Thanks for the issue, and for the example repo. I believe this was an intentional breaking change for Rails 6.1. This would have worked in Rails 6.0, but it would have output a deprecation warning. I double checked using your example repo back on Rails 6.0.3.4 and I can see this in log/development.log:
In general I would recommend addressing all deprecation warnings before upgrading to the next version.
Looks like 43863bfc5716d8bd2f5be59920239aee1470465a changed this behavior, so I think I was wrong about it being an intentional breaking change. But it would have eventually been an intentional breaking change 🤣.