Rails 7 控制器装饰器仅在生产中出现未初始化的常量错误
我收到以下错误 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这里的问题是,当延迟加载时,没有人引用名为
...::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 recursiverequire
. 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). Recursiveconst_get
still issuesrequire
calls viaModule#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.