Rails 7 控制器装饰器仅在生产中出现未初始化的常量错误

发布于 2025-01-18 20:30:26 字数 4921 浏览 3 评论 0原文

我收到以下错误 zeitwerk/loader/helpers.rb:95:in const_get': uninitializedconstant Controllers::BasePublicDecorator (NameError) 这是使用 rails c -e production 的本地生产控制台中的错误,但不是完美运行的开发中的问题。

在引擎 CcsCms::PublicTheme 中,我有一个装饰器,用于扩展另一个 CcsCms::Core 引擎的控制器,正是这个装饰器导致了错误。

public_theme/app/decorators/decorators/controllers/base_public_decorator.rb

CcsCms::BasePublicController.class_eval do
  before_action :set_theme #ensure that @current_theme is available for the
                          #header in all public views

  private

    def set_theme
      @current_theme = CcsCms::PublicTheme::Theme.current_theme
    end
end

此功能在开发中运行良好,但在生产中失败并出现如下错误

我试图在 CcsCms::Core 引擎中装饰的控制器是 CcsCms::BasePublicController.rb

module CcsCms
  class BasePublicController < ApplicationController
    layout "ccs_cms/layouts/public"

    protected
      def authorize
      end
  end
end

在带有装饰器的主题引擎中,我尝试使用我有一个 Gemfile,它定义了核心引擎,如下

gem 'ccs_cms_core', path: '../core'

所示ccs_cms_public_theme.gemspec 我需要核心引擎作为

  spec.add_dependency "ccs_cms_core"

engine.rb中的依赖项我需要核心引擎并在config.to_prepare do块中加载装饰器路径

require "deface"
require 'ccs_cms_admin_dashboard'
require 'ccs_cms_custom_page'
require 'ccs_cms_core'
require 'css_menu'
#require 'tinymce-rails'
require 'delayed_job_active_record'
require 'daemons'
require 'sprockets/railtie'
require 'sassc-rails'

module CcsCms
  module PublicTheme
    class Engine < ::Rails::Engine
      isolate_namespace CcsCms::PublicTheme
      paths["app/views"] << "app/views/ccs_cms/public_theme"

      initializer "ccs_cms.assets.precompile" do |app|
        app.config.assets.precompile += %w( public_theme_manifest.js )
      end

      initializer :assets do |config|
        Rails.application.config.assets.paths << root.join("")
      end

      initializer :append_migrations do |app|
        unless app.root.to_s.match?(root.to_s)
          config.paths['db/migrate'].expanded.each do |p|
            app.config.paths['db/migrate'] << p
          end
        end
      end

      initializer :active_job_setup do |app|
        app.config.active_job.queue_adapter = :delayed_job
      end

      config.to_prepare do
        Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
          Rails.configuration.cache_classes ? require(c) : load(c)
        end
      end

      config.generators do |g|
        g.test_framework :rspec,
          fixtures: false,
          request: false,
          view_specs: false,
          helper_specs: false,
          controller_specs: false,
          routing_specs: false
        g.fixture_replacement :factory_bot
        g.factory_bot dir: 'spec/factories'
      end

    end
  end
end

鉴于我的装饰器被赋予相同的名称作为控制器,它是从核心引擎装饰的,但通过 .decorator 扩展,我非常确定一切都正确连接,如上所述,这在开发中完美运行,但我无法启动 Rails由于此错误,在生产环境中使用控制台。 看来 class_eval 不知何故失败了,我只能认为这可能是路径问题,但我无法弄清楚

更新 经过相当长的学习曲线后,非常感谢@debugger 评论和@Xavier Noria 答案很明显,我的问题归结为Zeitworks自动加载功能

Rails指南此处 对我来说有一个有趣且有吸引力的解决方案

另一个用例是引擎装饰框架类:

initializer "decorate ActionController::Base" do  
> ActiveSupport.on_load(:action_controller_base) do
>     include MyDecoration   end end

此时,模块对象存储在 MyDecoration 中 初始化程序运行成为 ActionController::Base 的祖先,并且 重新加载 MyDecoration 是没有意义的,它不会影响那个祖先 链。

但也许这不是正确的解决方案,我再次未能使其与以下

  initializer "decorate CcsCms::BasePublicController" do
    ActiveSupport.on_load(:ccs_cms_base_public_controller) do
      include CcsCms::BasePublicDecorator
    end
  end

生成以下错误

zeitwerk/loader/callbacks.rb:25:in `on_file_autoloaded': expected file /home/jamie/Development/rails/comtech/r7/ccs_cms/engines/public_theme/app/decorators/controllers/ccs_cms/base_public_decorator.rb to define constant Controllers::CcsCms::BasePublicDecorator, but didn't (Zeitwerk::NameError)

一起工作所以回到提供的解决方案 这里,再次感谢下面的答案我尝试了以下方法,最终成功了

  config.to_prepare do
    overrides = Engine.root.join("app", "decorators")
    Rails.autoloaders.main.ignore(overrides)
    p = Engine.root.join("app", "decorators")
    loader = Zeitwerk::Loader.for_gem
    loader.ignore(p)
    Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
      Rails.configuration.cache_classes ? require(c) : load(c)
    end
  end

I am getting the following error zeitwerk/loader/helpers.rb:95:in const_get': uninitialized constant Controllers::BasePublicDecorator (NameError)
This is an error in a local production console using rails c -e production but not an issue in development which works perfectly.

In an engine, CcsCms::PublicTheme, I have a decorator I am using to extend the controller of another CcsCms::Core engine and it is this decorator that is causing the error.

public_theme/app/decorators/decorators/controllers/base_public_decorator.rb

CcsCms::BasePublicController.class_eval do
  before_action :set_theme #ensure that @current_theme is available for the
                          #header in all public views

  private

    def set_theme
      @current_theme = CcsCms::PublicTheme::Theme.current_theme
    end
end

This functionality is working perfectly in development but fails in production with an error as follows

The controller I am trying to decorate in the CcsCms::Core engine is CcsCms::BasePublicController.rb

module CcsCms
  class BasePublicController < ApplicationController
    layout "ccs_cms/layouts/public"

    protected
      def authorize
      end
  end
end

in the theme engine with the decorator I am trying to use I have a Gemfile that defines the core engine as follows

gem 'ccs_cms_core', path: '../core'

In the ccs_cms_public_theme.gemspec I am requiring the core engine as a dependency

  spec.add_dependency "ccs_cms_core"

in the engine.rb I am requiring the core engine and loading the decorator paths in a config.to_prepare do block

require "deface"
require 'ccs_cms_admin_dashboard'
require 'ccs_cms_custom_page'
require 'ccs_cms_core'
require 'css_menu'
#require 'tinymce-rails'
require 'delayed_job_active_record'
require 'daemons'
require 'sprockets/railtie'
require 'sassc-rails'

module CcsCms
  module PublicTheme
    class Engine < ::Rails::Engine
      isolate_namespace CcsCms::PublicTheme
      paths["app/views"] << "app/views/ccs_cms/public_theme"

      initializer "ccs_cms.assets.precompile" do |app|
        app.config.assets.precompile += %w( public_theme_manifest.js )
      end

      initializer :assets do |config|
        Rails.application.config.assets.paths << root.join("")
      end

      initializer :append_migrations do |app|
        unless app.root.to_s.match?(root.to_s)
          config.paths['db/migrate'].expanded.each do |p|
            app.config.paths['db/migrate'] << p
          end
        end
      end

      initializer :active_job_setup do |app|
        app.config.active_job.queue_adapter = :delayed_job
      end

      config.to_prepare do
        Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
          Rails.configuration.cache_classes ? require(c) : load(c)
        end
      end

      config.generators do |g|
        g.test_framework :rspec,
          fixtures: false,
          request: false,
          view_specs: false,
          helper_specs: false,
          controller_specs: false,
          routing_specs: false
        g.fixture_replacement :factory_bot
        g.factory_bot dir: 'spec/factories'
      end

    end
  end
end

Given that my decorator is given the same name as the controller it is decorating from the core engine but with the .decorator extension I am pretty certain that is everything hooked up correctly, as mentioned, this works perfectly in development but I am unable to start a rails console in a production environment due to this error.
It seems that the class_eval is failing somehow and I can only think that this may be a path issue but I can not figure it out

UPDATE
After quite a big learning curve, thank's muchly to @debugger comments and @Xavier Noria
answer it is clear that my issue comes down to Zeitworks autoload functionality

Rails guides here has an interesting and appealing solution to me

Another use case are engines decorating framework classes:

initializer "decorate ActionController::Base" do  
> ActiveSupport.on_load(:action_controller_base) do
>     include MyDecoration   end end

There, the module object stored in MyDecoration by the time the
initializer runs becomes an ancestor of ActionController::Base, and
reloading MyDecoration is pointless, it won't affect that ancestor
chain.

But maybe this isn't the right solution, I again failed to make it work with the following

  initializer "decorate CcsCms::BasePublicController" do
    ActiveSupport.on_load(:ccs_cms_base_public_controller) do
      include CcsCms::BasePublicDecorator
    end
  end

Generating the following error

zeitwerk/loader/callbacks.rb:25:in `on_file_autoloaded': expected file /home/jamie/Development/rails/comtech/r7/ccs_cms/engines/public_theme/app/decorators/controllers/ccs_cms/base_public_decorator.rb to define constant Controllers::CcsCms::BasePublicDecorator, but didn't (Zeitwerk::NameError)

So back to the solution provided here, thank's again for the answer below I tried the following which did work finally

  config.to_prepare do
    overrides = Engine.root.join("app", "decorators")
    Rails.autoloaders.main.ignore(overrides)
    p = Engine.root.join("app", "decorators")
    loader = Zeitwerk::Loader.for_gem
    loader.ignore(p)
    Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
      Rails.configuration.cache_classes ? require(c) : load(c)
    end
  end

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

策马西风 2025-01-25 20:30:26

这里的问题是,当延迟加载时,没有人引用名为 ...::BasePublicDecorator 的常量。然而,Zeitwerk 期望在该文件中定义该常量,并且在急切加载时发现不匹配。

解决方案是将自动加载器配置为忽略装饰器,因为您正在处理它们的加载,并且它们在名称后没有定义常量。 本文档有一个示例。它需要适应您的引擎,但您会明白这个想法。

为了完整起见,我还要解释一下,在 Zeitwerk 中,急切加载是递归的 const_get,而不是递归的 require。这是为了保证如果您访问常量,加载在两种模式下一致成功或失败(而且效率也更高)。递归 const_get 仍然通过 Module#autoload 发出 require 调用,如果您对某些文件运行一个调用,幂等性也适用,但 Zeitwerk 会检测到预期的常量无论如何都没有定义,这是一个错误条件。

Problem here is that when lazy loading, nobody is referencing a constant called ...::BasePublicDecorator. However, Zeitwerk expects that constant to be defined in that file, and the mismatch is found when eager loading.

The solution is to configure the autoloader to ignore the decorators, because you are handling their loading, and because they do not define constants after their names. This documentation has an example. It needs to be adapted to your engine, but you'll see the idea.

For completeness, let me also explain that in Zeitwerk, eager loading is a recursive const_get, not a recursive require. This is to guarantee that if you access the constant, loading succeeds or fails consistently in both modes (and it is also a tad more efficient). Recursive const_get still issues require calls via Module#autoload, and if you ran one for some file idempotence also applies, but Zeitwerk detects the expected constant is not defined anyway, which is an error condition.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文