Reputation: 1549
I am implementing a singleton class/module in Rails 6 application using Zeitwerk loader.
# app/lib/mynamespace/mymodel.rb
module Mynamespace
module Mymodel
class << self
attr_accessor :client
end
def self.client
@client ||= "default_value"
end
def self.client=(client)
@client = client
end
end
Singleton class is initialized in
# config/initializers/mymodel.rb
Mynamespace::Mymodel.client = "my_custom_value"
# Mynamespace::Mymodel.client - this returns correct value
Then when I use the singleton class in a controller
# app/controllers/mycontroller.rb
client = Mynamespace::Mymodel.client
it returns an empty object as it was not initialized: client == "default_value" but should be "my_custom_value".
Log shows errors
DEPRECATION WARNING: Initialization autoloaded the constants Mynamespace::Mymodel
Autoloading during initialization is going to be an error condition in future versions of Rails.
How to properly configure a singleton class while using Zeitwerk ?
Upvotes: 1
Views: 2736
Reputation: 18784
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.
Move that code out of config/initializers
, and into config/application.rb
, and it will still be run on boot:
module MyApp
class Application < Rails::Application
# other stuff
config.after_initialize do
Mynamespace::MyModel.client = "my_custom_value"
end
end
end
Upvotes: 2
Reputation: 2324
This is why referring reloadable constants has been finally forbidden in Rails 7, because it doesn't make sense and you find the hard way.
This is unrelated to Zeitwerk, it is related to logic about reloading itself.
TLDR: Since code in app/lib
is reloadable (that is why you put it there), you need to consider that on reload the initialization has to happen again. That is accomplished with a to_prepare
block. Please have a look at https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoloading-when-the-application-boots.
On the other hand, if you are good about not reloading that singleton, then you can move it to the top-level lib
and issue a require
for it in the initializers. (Assuming that lib
is not in the autoload paths, which is not by default.)
Upvotes: 1