rails: Unexpected deprecation warning in Rails 6: 'Initialization autoloaded the constant Example'
Steps to reproduce
- Create a new app with Rails 6.0.0.rc1 (or tip of
6-0-stable
branch) - The app initializes fine. Confirm with
rails test
. - Define a simple ActiveModel::Type
# app/types/example.rb
class Example < ActiveModel::Type::String
end
# config/initializers/types.rb
ActiveRecord::Type.register(:example, Example)
- Now the app initializes with deprecation warnings. Confirm with
rails test
.
Expected behavior
No Rails deprecation warnings. This is a bare app, using a simple Rails feature as documented.
Actual behavior
$ rails test
DEPRECATION WARNING: Initialization autoloaded the constant Example.
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 Example, for example,
the expected changes won't be reflected in that stale Class object.
This autoloaded constant has been unloaded.
Please, check the "Autoloading and Reloading Constants" guide for solutions.
(called from <top (required)> at /Users/ahoyt/code/tiu/testApp/config/environment.rb:5)
System configuration
Rails version: 6.0.0.rc1 (or with tip of 6-0-stable
branch)
Ruby version: 2.5.5
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 24
- Comments: 22 (16 by maintainers)
Commits related to this issue
- Avoid deprecation warning for constants autoloaded in initialization Based on https://github.com/rails/rails/issues/36363 and Rails guides https://edgeguides.rubyonrails.org/autoloading_and_reloading... — committed to PopulateTools/gobierto by entantoencuanto 4 years ago
- Avoid deprecation warning for constants autoloaded in initialization Based on https://github.com/rails/rails/issues/36363 and Rails guides https://edgeguides.rubyonrails.org/autoloading_and_reloading... — committed to PopulateTools/gobierto by entantoencuanto 4 years ago
- Avoid deprecation warning for constants autoloaded in initialization Based on https://github.com/rails/rails/issues/36363 and Rails guides https://edgeguides.rubyonrails.org/autoloading_and_reloading... — committed to PopulateTools/gobierto by entantoencuanto 4 years ago
Hi!
My understanding of this problem is that when the application encounter
ActiveRecord::Type.register(:example, Example)
in your initializer, this is the first timeExample
is referenced, so Rails will try to autoload it. As the deprecation warning message says, Autoloading during initialization is not good because it will not reload correctly.Wrapping your initializer with
ActiveSupport.on_load(:active_record)
will wait until ActiveRecord has finished loading. By now, your fileexample.rb
would have been autoloaded as part of theapp
folder being loaded. When the content of the initializer is executed, it will not be the first reference toExample
, so rails won’t have to try to autoload it. This also has the advantage of preventing an early load of ActiveRecord.In your
config/initializers/types.rb
:@fxn or somebody more knowledgeable in Rails Autoloading could probably give you a more authoritative answer.
Ah, of course, that makes sense! Thanks @fxn for the explanation!
I forgot that any
app/*
paths are autoloaded! So, I introduced this autoloaded path into the types initializer…and fully deserved the warning.The deprecation warning about autoloaded constants went away when I followed your suggestion. I moved my types to
lib
and simply required them directly:Now, all my types work, my tests pass, and neither
app/types
orlib/types
are in my autoload_paths.Happy dance! 😀 🎉 🕺 🥂
@jackkinsella could you please try this?
Define
CurrencyType
as you did before, without the line that registers.In an initializer register in a
to_prepare
block:If
register
overwrites the previous:currency
entry as I believe, that should do the trick and the class would be reloadable.Not quite. Let me explain.
require_dependency
should not be used, that helper is related to autoloading in classic mode, and it keeps track of reloadable constants. Applications running inzeitwerk
mode should not userequire_dependency
.What you want, is to load the type, register it, and done. You do not want the class object stored in the constant to become stale if you edit the file and reload. You want the class object to be valid for the entire lifetime of the process regardless of reloads.
So, you have lib/types/example.rb, and in the initializer you
which is going to work because lib belongs to
$LOAD_PATH
.This is not something related to Zeitwerk.
The type is registered in an initializer. No matter whether you use
classic
orzeitwerk
mode, if you edit your custom type and reload, changes won’t take effect because initializers run only when the application boots.So before Rails 6, you could think the edit had effect, but it did not. Advising against this is the point of the warning in Rails 6.
I believe it should be possible to get it working if you registered at the bottom of the file that implements the custom type (because I believe the register would override the entry with the same key), and configured a callback to force loading that constant. But I don’t have time right now to test that idea.
Hey @jasonperrone, could you please read sections 6.1 and 6.2 in the edge autoloading guide, and see if that solves your use case?
If you edit app/types/example.rb, the registered class object will become stale, won’t reflect the changes. One way to do this cleanly is to move the types directory to lib, and perform a regular
require
in the initializer.Not really. It would be confusing and a potential issue to have a directory in
autoload_paths
for files that do not have to be autoloaded nor reloaded.As I said above, better move that to lib.
There is a Rails Guide, Autoloading and Reloading Constants (Zeitwerk Mode). Check out section 6.1 Reloading and Stale Objects.
The docs for the attribute method are pretty good for how to work with types, but it doesn’t warn about the pitfalls of how you load your custom types. That would make a good addition!
To fix this exception, just make sure you don’t autoload your custom types, like I did, but put them in
/lib
. See comment above for how I load my custom types.btw, the above advice from @fxn is solid. He wrote zeitwerk.
Thanks @fxn for helping to sort that out. Might suggest adding something about requiring deps in initializers in the autoloading section of the upgrade guide.
For @ansonhoyt , I think he just needs
require 'example'
since files in “app/types/” should already be autoloaded.