Rails6Zeitwerk在初始化后卸载我的类



我正在使用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不在自动加载路径中,这在默认情况下不是。(

最新更新