Rails 6 с Zeitwerk не загружает класс из модуля

У меня есть файл, который выглядит так

#app/services/account/authenticate/base.rb
module Account
  module Authenticate
    AuthenticateError = Class.new(StandardError)

    class Base < ::Account::Base
      def self.call(*attrs)
        raise NotImplementedError
      end
    end
  end
end

Теперь, когда я буду запускать код из rails c У меня ошибка

> ::Account::Authenticate::AuthenticateError
=> NameError (uninitialized constant Account::Authenticate::AuthenticateError)
> ::Account::Authenticate.constants
=> [:Base, :ViaToken]

Поэтому rails не видит класс AuthenticateError. Но когда я создам вложенный класс из этой папки, как

=> Account::Authenticate::ViaToken
> ::Account::Authenticate.constants
=> [:Base, :AuthenticateError, :ViaToken]

Класс AuthenticateError теперь виден

> ::Account::Authenticate::AuthenticateError
=> Account::Authenticate::AuthenticateError

Решением этой проблемы является создание отдельного файла authenticate_error.rb, который будет работать с самого начала, но это решение не идеально для меня. Есть ли решение для предварительной загрузки всех классов или что-то?

(Ruby 2.6 с Rails 6.0.0.rc2)

4 ответа

У меня возникла та же проблема при развертывании приложения Rails 6.0.2 на сервере Ubuntu 18.04.

Невозможно загрузить приложение: Zeitwerk::NameError: ожидался файл /home/deploy/myapp/app/models/concerns/designation.rb для определения обозначения константы, но не

Я выяснил, что проблема была в zeitwerk. Zeitwerk - это новый движок загрузчика кода, используемый в Rails 6. Он должен быть новым по умолчанию для всех проектов Rails 6+, заменяющих старый классический движок. Zeitwerk предоставляет функции автозагрузки, активной загрузки и перезагрузки кода.

Вот как я это решил:

Перейдите к config/application.rb файл в вашем проекте.

Добавьте эту строку в свой модуль приложения, чтобы переключиться на classic режим автозагрузки:

config.autoloader = :classic

Вот пример:

module MyApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    config.autoloader = :classic
  end
end

Вы можете узнать больше о zeitwerk в этой статье: Понимание Zeitwerk в Rails 6

Вот и все.

надеюсь, это поможет

обновить приложение Rails с 5.2 до 6.0, а также попасть в Zeitwerk!

Если вы хотите продолжать использовать режим автозагрузки, который вы используете в настоящее время, избегая Zeitwerk, добавьте эту строку в свой файл application.rb (ответ @PromisePreston и документ Rails )

      config.autoloader = :classic

Если вы хотите перейти на Zeitwerk, используйте следующую команду: bin/rails zeitwerk:check(Из этой справочной статьи ).

Сценарий, который мы ближе всего подошли к этому конкретному вопросу, заключался в том, что у нас был файл в такой подпапке:

      #presenters/submission_files/base.rb
module Presenters
  module SubmissionFiles
    class Base < Showtime::Presenter
      def method_call
        #code_here
      end
    end
  end
end

Удаление дополнительных модулей, чтобы иметь:

       #presenters/submission_files/base.rb
 module Presenters
   class SubmissionFiles::Base < Showtime::Presenter
      def method_call
        #code_here
      end
   end
 end

Затем при вызове метода в других рубиновых файлах приложения используйте: Presenters::SubmissionFiles::Base.method_call

следить conventional file structure который может загружать классы и модули вашего проекта по запросу (автозагрузка), если вы следуете его правилу.

      # service/account/authenticate/base.rb
module Account
  module Authenticate
    puts "load service ....."
    AuthenticateError = Class.new(StandardError)

    class Base
    end
  end
end

::Account::Authenticate::AuthenticateError # uninitialized constant
::Account::Authenticate::Base # load service ....
::Account::Authenticate::AuthenticateError # OK

как видите, при первой попытке достичь константы журнал load service ... не показывает, что из-за того, что вы не играете в правила:

  • всякий раз, когда он получает запрос на загрузку константы, сначала он проверяет и возвращает, если эта константа уже загружена, в противном случае он будет искать файл, соответствующий константе, чтобы найти это определение константы, но не может его найти.

  • на шаге 2, когда вы звоните ::Account::Authenticate::Base, он может найти файл /account/authenticate/base.rb и загрузите его, в это время он также загрузит константу, которая определена в этом файле, теперь у нас есть константа ::Account::Authenticate::AuthenticateError, и, конечно же, на шаге 3 все в порядке.

теперь давайте попробуем поиграться с правилом, я создаю файл /account/authenticate/authenticate_error.rb как показано ниже

      # service/account/authenticate/authenticate_error.rb
module Account
  module Authenticate
    puts "load error ....."
    AuthenticateError = Class.new(StandardError)
  end
end

и попробуйте попробовать эту константу на шаге 1

      $ spring stop
$ rails c
> ::Account::Authenticate::AuthenticateError
load error .....
=> Account::Authenticate::AuthenticateError

это работало с zeitwerk нашел файл account/authenticate/authenticate_error.rb. (обратите внимание, что имя файла /____authenticate_error.rb все еще работают)

моя мысль: я думаю, вы могли бы безопасно работать с константой внутри модуля ::Account::Authenticate, в случае, если вы хотите выставить эти константы ошибок извне, вы можете создать файл /account/authenticate/error.rb

      # service/account/authenticate/error.rb
module Account
  module Authenticate
    module Error
     AuthenticateError = Class.new(StandardError)
    end
  end
end

тогда вы могли бы получить доступ ::Account::Authenticate::Error::AuthenticateError, на мой взгляд, это даже понятнее, чем поставить AuthenticateError внутри base.rb.

Я смог исправить это, не пытаясь бороться с Rails 6. Zeitwerk автоматически загружает определенные ожидаемые папки, включая приложения / модели, приложения / контроллеры, приложения / помощники и другие.

Я создал папку app/helpers и переместил мой services папку в него.

Вот и все!

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