我正在使用Zeitwerk加载程序在Rails6应用程序中实现一个单例类/模块。
# 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类在中初始化
# config/initializers/mymodel.rb
Mynamespace::Mymodel.client = "my_custom_value"
# Mynamespace::Mymodel.client - this returns correct value
然后当我在控制器中使用singleton类时
# app/controllers/mycontroller.rb
client = Mynamespace::Mymodel.client
它返回一个空对象,因为它没有初始化:client=="defaultvalue",但应该是"mycustom_value"。
日志显示错误
DEPRECATION WARNING: Initialization autoloaded the constants Mynamespace::Mymodel
Autoloading during initialization is going to be an error condition in future versions of Rails.
如何在使用Zeitwerk时正确配置singleton类?
我认为这里的问题是Zeitwerk加载代码的方式,它首先从Gemfile加载Gem,然后运行初始化程序,然后加载应用程序代码,所以尝试运行Mynamespace::MyModel.client
意味着它必须停止它正在做的事情,加载app/lib/mynamespace/mymodel.rb
来加载常量,并在上面执行client=
。
这也意味着,如果您更改Mynamespace::MyModel
代码,Rails将无法热重新加载常量,因为初始化程序不会重新运行,从而引入循环依赖锁(您是否见过类似"module MyModel已从树中删除,但仍处于活动状态!"的错误,或者在使用一些应该自动加载但没有的代码之前必须使用require_dependency
?(。Zeitwerk试图解决这类问题。
将该代码从config/initializers
移到config/application.rb
中,它仍将在引导时运行。
这就是为什么在Rails7中最终禁止引用可重载常量的原因,因为这没有意义,而且很难找到。
这与Zeitwerk无关,它与重新加载自身的逻辑有关。
TLDR:由于app/lib
中的代码是可重新加载的(这就是您将其放在那里的原因(,因此您需要考虑在重新加载时必须再次进行初始化。这是通过to_prepare
块来实现的。请看一下https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoloading-当应用程序启动时。
另一方面,如果您擅长不重新加载该singleton,那么您可以将其移动到顶级lib
,并在初始化器中为其发出require
。(假设lib
不在自动加载路径中,这在默认情况下不是。(