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отсутствует в путях автозагрузки, чего нет по умолчанию.)

Другие вопросы по тегам