Rails 7 controller decorator uninitialized constant error only in production 只
Rails 7 controller decorator uninitialised constant error in production only
我收到以下错误 zeitwerk/loader/helpers.rb:95:in const_get': uninitialized constant 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 指南 here 对我来说有一个有趣且有吸引力的解决方案
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.
但也许这不是正确的解决方案,我再次未能使其适用于以下
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)
所以回到提供的解决方案 here,再次感谢下面的回答 我尝试了以下最终有效的方法
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
这里的问题是延迟加载时,没有人引用名为 ...::BasePublicDecorator
的常量。但是,Zeitwerk 期望在该文件中定义该常量,并且在急切加载时发现不匹配。
解决方案是将自动加载器配置为忽略装饰器,因为您正在处理它们的加载,并且因为它们没有在名称后定义常量。 This documentation 有一个例子。它需要适应您的引擎,但您会看到这个想法。
为了完整起见,我还要说明一下,在Zeitwerk中,预加载是递归的const_get
,而不是递归的require
。这是为了确保如果您访问常量,加载成功或失败在两种模式下始终如一(而且效率更高)。递归 const_get
仍然通过 Module#autoload
发出 require
调用,如果你 运行 一个用于某些文件的幂等性也适用,但 Zeitwerk 检测到预期的常量无论如何都没有定义,这是错误情况。
我收到以下错误 zeitwerk/loader/helpers.rb:95:in const_get': uninitialized constant 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 指南 here 对我来说有一个有趣且有吸引力的解决方案
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.
但也许这不是正确的解决方案,我再次未能使其适用于以下
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)
所以回到提供的解决方案 here,再次感谢下面的回答 我尝试了以下最终有效的方法
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
这里的问题是延迟加载时,没有人引用名为 ...::BasePublicDecorator
的常量。但是,Zeitwerk 期望在该文件中定义该常量,并且在急切加载时发现不匹配。
解决方案是将自动加载器配置为忽略装饰器,因为您正在处理它们的加载,并且因为它们没有在名称后定义常量。 This documentation 有一个例子。它需要适应您的引擎,但您会看到这个想法。
为了完整起见,我还要说明一下,在Zeitwerk中,预加载是递归的const_get
,而不是递归的require
。这是为了确保如果您访问常量,加载成功或失败在两种模式下始终如一(而且效率更高)。递归 const_get
仍然通过 Module#autoload
发出 require
调用,如果你 运行 一个用于某些文件的幂等性也适用,但 Zeitwerk 检测到预期的常量无论如何都没有定义,这是错误情况。