Ran into something interesting while trying to use ActiveSupport::Configurable in Rails 6. It appears the OrderedHash created by ActiveSupport::Configurable is being overwritten in my development environment only.
I have a module Foo with a class Bar as follows:
module Foo
class Bar
include ActiveSupport::Configurable
def self.request_body
{
'client_id' => my_client_id,
'client_secret' => my_client_secret
}
end
end
end
and its corresponding initializer
Foo::Bar.configure do |c|
if Rails.env.production? || Rails.env.staging?
%i[my_client_id my_client_secret].each do |key|
# set for prod and staging using Rails credentials
end
elsif Rails.env.test?
%i[my_client_id my_client_secret].each do |key|
# set for test
end
else
%i[my_client_id my_client_secret].each do |key|
c[key] = '1234'
end
end
end
If I insert byebug at the end of my initializer, start the rails console and call
But when I exit byebug and reach the console prompt and run the above I get an empty hash.
I have found two “workaround” solutions. In development.rb, setting config.cache_classes to true allows the OrderedHash to be set as expected. Likewise, reverting to the classic autoloader in application.rb with config.autoloader = :classic produces the expected outcome.
No warnings. Running rails s produces the typical:
=> Booting Puma
=> Rails 6.0.3.3 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 5.0.2 (ruby 2.5.1-p57), codename: Spoony Bard
* Min threads: 5, max threads: 5
* Environment: development
* Listening on http://0.0.0.0:3000
Use Ctrl-C to stop
I have several modules and it requires some api keys. For exmaple bp credit card login
MyModule.configure do |config|
config.api_key = '...'
end
And all of sudden, it is not working only in development. It seems that it does not load this configure block as initially so my module does not know what the keys are. Any idea?
Every time I encounter values not being set from an initializer, I blame Spring. Try using spring stop in your project folder, and see if that wakes things up.
I believe the issue here is that the way Zeitwerk loads your code, it’s first loading Gems from your Gemfile, then running initializers, then loading your application code, so trying to run Mynamespace::MyModel.client , means it has to stop what it’s doing and load app/lib/mynamespace/mymodel.rb to load that constant, to execute client= on it.
This also means that if you change the Mynamespace::MyModel code, Rails will not be able to hot-reload the constant, because initializers don’t get re-run, introducing a circular dependency lock (have you ever seen an error like “module MyModel removed from tree but still active!” or have to use require_dependency before using some code that should be autoloaded but isn’t?). Zeitwerk attempts to fix that class of issues Kroger Feed
Move that code out of config/initializers , and into config/application.rb , and it will still be run on boot.
If MyModule is autoloaded, the problem is that this module will be redefined when a reload happens. Initializers only run when the application boots, so nobody is setting the API key again on reload. Let me break it down:
Application boots, the initializer is executed, MyModule autoloaded, and the API key set.
A reload happens.
MyModule is reloaded, but the initializer does not run again, therefore the fresh new module does not have an API key set.
This is not specific to Zeitwerk, it has been this way always.
However, in Rails 6 we make that more apparent so that you are aware of the latent issue, and a verbose warning is issued. I have in mind to make autoloading during initialization an error condition in the future, because this gotcha is very common.
Please check these docs for a way to do that correctly. Even easier, if you upgrade Zeitwerk to the last version you can write that initializer this way:
Rails.autoloaders.main.on_load("MyModule") do
MyModule.configure do |config|
config.api_key = '...'
end
end
Maybe there is going to be API for that in the future, but for Rails 6.1 it is better not to force the last version of the library as a gem dependency.