【问题标题】:Rails 6: Zeitwerk::NameError doesn't load class from moduleRails 6:Zeitwerk::NameError 不从模块加载类
【发布时间】:2019-07-30 17:48:37
【问题描述】:

我有一个看起来像这样的文件

#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,它可以从一开始就起作用,但这个解决方案对我来说并不理想。有没有办法预加载所有类或smth?

(Ruby 2.6 与 Rails 6.0.0.rc2)

【问题讨论】:

  • 你试过在config/environments/development中设置config.eager_load = true吗?
  • @jvillian 它不起作用
  • 您是否偶然使用了弹簧?
  • @jvillian 是的,spring 和所有 Rails 服务器在更改后停止/重新启动
  • 即使没有春天我也经历过。但是,只有当我的模块位于非 railsy 目录并嵌套时才会发生这种情况。 IE。我可以添加config.autoload_paths += Dir[Rails.root.join('app', 'service_objects', '{*/}')],它将包含 service_objects 中的所有内容。我还可以将模块添加到 /app/models 的子目录中。但是,对于嵌套在非 rails 目录中的文件,同样的方法不起作用。

标签: ruby-on-rails ruby


【解决方案1】:

我在将 Rails 6.0.2 应用程序部署到 Ubuntu 18.04 服务器时遇到了同样的问题。

无法加载应用程序:Zeitwerk::NameError: 预期文件 /home/deploy/myapp/app/models/concerns/designation.rb 来定义常量指定,但没有

我发现问题出在 zeitwerk 上。 Zeitwerk 是 Rails 6 中使用的新代码加载器引擎。它旨在成为所有 Rails 6+ 项目的新默认值,以取代旧的 classic 引擎。 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 的信息:Understanding Zeitwerk in Rails 6

就是这样。

我希望这会有所帮助

【讨论】:

  • 不太适合我。我现在不得不放弃config.load_defaults 6.0。我希望这篇文章和指南能让我快速了解我哪里出错了。
  • 即使我必须删除 config.load_defaults 6.0 才能使其工作。有什么帮助吗?
【解决方案2】:

zeitwerkfollow 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

如您所见,第一次尝试到达常量AuthenticateError 时,日志load service ... 没有显示,那是因为您没有玩zeitwerk 规则:

  • 当它收到加载常量::Account::Authenticate::AuthenticateError的请求时,首先它会检查并返回该常量是否已经加载,否则它会查找与常量::Account::Authenticate::AuthenticateError对应的文件/account/authenticate/authenticate_error.rb来查找那个常量定义,但是找不到。

  • 在第2步,当你调用::Account::Authenticate::Base时,它可以找到/account/authenticate/base.rb文件并加载它,在此期间,它也加载了在该文件上定义的常量AuthenticateError,现在我们有了常量::Account::Authenticate::AuthenticateError,当然第3步就OK了。

现在让我们尝试使用zeitwerk 规则,我创建一个文件/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 内的常量 AuthenticateError,如果您想将这些错误常量暴露给外部,您可以创建文件 /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 更清晰。

【讨论】:

    【解决方案3】:

    将 Rails 应用程序从 5.2 更新到 6.0,还遇到了 Zeitwerk 的问题!

    如果您想继续使用您当前使用的自动加载模式,避免使用 Zeitwerk,则将此行添加到您的 application.rb 文件(@PromisePreston answerRails doc

    config.autoloader = :classic
    

    如果您想升级到 Zeitwerk,那么要使用的命令是 bin/rails zeitwerk:check(来自此 guide article)。

    我们最接近这个特定问题的场景是我们在这样的子文件夹中有一个文件:

    #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
    

    然后,在应用程序中调用其他 ruby​​ 文件中的方法时,使用:Presenters::SubmissionFiles::Base.method_call

    【讨论】:

      【解决方案4】:

      我能够通过不尝试与 Rails 6 对抗来修复它。Zeitwerk 自动加载某些预期的文件夹,包括 app/models、app/controllers、app/helpers 等。

      我创建了一个文件夹 app/helpers 并将我的 services 文件夹移到其中。

      就是这样!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-05
        • 2023-01-19
        • 1970-01-01
        • 2018-08-26
        • 1970-01-01
        相关资源
        最近更新 更多