Rails 6 Zeitwerk выгружает мой класс после инициализатора
Я реализую одноэлементный класс / модуль в приложении Rails 6 с помощью загрузчика Zeitwerk.
# 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
Затем, когда я использую одноэлементный класс в контроллере
# app/controllers/mycontroller.rb
client = Mynamespace::Mymodel.client
он возвращает пустой объект, поскольку он не был инициализирован: client == "default_value", но должен быть "my_custom_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?
2 ответа
Я считаю, что проблема здесь в том, как Zeitwerk загружает ваш код: сначала он загружает Gems из вашего Gemfile, затем запускает инициализаторы, затем загружает код вашего приложения, поэтому пытается запустить Mynamespace::MyModel.client
, означает, что он должен прекратить свои действия и загрузить app/lib/mynamespace/mymodel.rb
чтобы загрузить эту константу, выполнить client=
в теме.
Это также означает, что если вы измените Mynamespace::MyModel
кода, Rails не сможет выполнить горячую перезагрузку константы, потому что инициализаторы не запускаются повторно, вводя циклическую блокировку зависимостей (вы когда-нибудь видели ошибку типа "модуль MyModel удален из дерева, но все еще активен!" или имели использовать require_dependency
перед использованием некоторого кода, который должен быть загружен автоматически, но этого не происходит?). Zeitwerk пытается исправить этот класс проблем.
Уберите этот код из config/initializers
, и в config/application.rb
, и он все равно будет запускаться при загрузке.
Вот почему ссылка на перезагружаемые константы была окончательно запрещена в Rails 7, потому что это не имеет смысла, и вы найдете трудный путь.
Это не связано с Zeitwerk, это связано с логикой перезагрузки.
TLDR: поскольку код в
app/lib
является перезагружаемым (поэтому вы его туда и поместили), вам нужно учитывать, что при перезагрузке инициализация должна произойти снова. Это достигается с помощью
to_prepare
блокировать. Пожалуйста, взгляните на https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoloading-when-the-application-boots.
С другой стороны, если вы хорошо относитесь к тому, чтобы не перезагружать этот синглтон, вы можете переместить его на верхний уровень и выдать
require
для него в инициализаторах. (При условии, что
lib
отсутствует в путях автозагрузки, чего нет по умолчанию.)