Ruby on Trailblazer API中NotificationCenter类冻结问题求助
解决Ruby Trailblazer API中的FrozenError:无法修改冻结的NotificationCenter类
问题概述
开发Ruby on Trailblazer API时,运行Rspec测试套件持续触发以下错误:
FrozenError: can't modify frozen #<Class:NotificationCenter>: NotificationCenter
报错位置在sftp.rb的module API定义行,该文件是首次定义NotificationCenter类的文件,但类定义后NotificationCenter.frozen?返回true,仓库中无显式调用freeze的代码,无法定位冻结原因。
相关代码(sftp.rb)
# frozen_string_literal: true require 'grape' require 'concepts' class NotificationCenter < NotificationCenter.superclass module API ### <- 报错行 ### class Notifications < Grape::API class Reports < Grape::API class Failed < Grape::API class Sftp < Grape::API resource :sftp do route_param :email do desc 'send notification to a specific user when report has failed to upload to sftp' params do optional :test, type: Boolean, default: true requires :email, type: String, coerce_with: Base64.method(:urlsafe_decode64) requires :report_name, type: String end post do result = run! Notification::Report::User, {}, 'email.cell' => Notification::Cell::Report::Failed::Sftp handle! result end end end end end end end end end
堆栈跟踪
An error occurred while loading ./spec/resources/notifications/statements/failed_spec.rb. Failure/Error: module API FrozenError: can't modify frozen #<Class:NotificationCenter>: NotificationCenter # ./app/resources/notifications/reports/failed/sftp.rb:7:in `<class:NotificationCenter>' # ./app/resources/notifications/reports/failed/sftp.rb:6:in `<top (required)>' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `block in require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:257:in `load_dependency' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `require' # ./app/resources/notifications/reports/failed.rb:7:in `glob' # ./app/resources/notifications/reports/failed.rb:7:in `<top (required)>' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `block in require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:257:in `load_dependency' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `require' # ./app/resources/notifications/reports.rb:7:in `glob' # ./app/resources/notifications/reports.rb:7:in `<top (required)>' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `block in require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:257:in `load_dependency' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `require' # ./app/resources/notifications.rb:7:in `glob' # ./app/resources/notifications.rb:7:in `<top (required)>' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `block in require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:257:in `load_dependency' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `require' # ./app/resources.rb:4:in `glob' # ./app/resources.rb:4:in `<top (required)>' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `block in require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:257:in `load_dependency' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `require' # ./app.rb:9:in `<top (required)>' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `block in require' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:257:in `load_dependency' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:291:in `require' # ./spec/resources/notifications/statements/failed_spec.rb:3:in `<top (required)>' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:285:in `load' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:285:in `block in load' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:257:in `load_dependency' # /usr/local/bundle/gems/activesupport-5.2.8.1/lib/active_support/dependencies.rb:285:in `load'
排查思路
- 检查Trailblazer框架行为:代码中引入了
concepts,Trailblazer的Concept类默认可能自动冻结,NotificationCenter继承自NotificationCenter.superclass(即Trailblazer的Concept基类),可能继承了冻结状态。 - 追踪冻结触发时机:添加TracePoint钩子定位
freeze调用来源,在NotificationCenter类定义后插入以下代码:
运行测试后会输出触发冻结的调用栈,直接定位原因。class NotificationCenter < NotificationCenter.superclass puts "NotificationCenter initialized, frozen? #{self.frozen?}" freeze_trace = TracePoint.new(:call) do |tp| if tp.method_id == :freeze && tp.self == NotificationCenter puts "=== Freeze triggered by: ===" tp.backtrace.each { |line| puts " #{line}" } freeze_trace.disable end end freeze_trace.enable # 原有API模块定义代码... end - 检查Zeitwerk加载配置:堆栈显示Zeitwerk负责文件加载,排查是否有自定义加载器钩子修改了类的冻结状态,或文件加载顺序导致
NotificationCenter被其他代码提前加载并冻结。 - 全局冻结设置:检查是否有全局Ruby配置(如
RubyVM::InstructionSequence.compile_option相关设置)或初始化代码批量冻结类。
解决方案
1. 临时解冻类后添加模块
如果冻结是框架自动触发且需要保留生产环境的冻结状态,可以在添加模块前临时解冻,完成后再恢复冻结:
class NotificationCenter < NotificationCenter.superclass # 临时解冻类以添加模块 self.unfreeze if frozen? module API # 原有API模块代码... end # 按需恢复冻结(如仅在生产环境) self.freeze if Rails.env.production? end
2. 调整Trailblazer配置
如果是Trailblazer的Concept类自动冻结,可修改框架配置关闭自动冻结(需参考Trailblazer官方文档中Concept类的冻结设置)。
3. 修复加载顺序问题
如果是Zeitwerk加载顺序导致类被提前冻结,调整文件命名或加载配置,确保sftp.rb是第一个加载NotificationCenter的文件,避免其他代码先加载并冻结该类。
内容的提问来源于stack exchange,提问作者Sandisz Thieme
相关产品推荐
相关产品推荐

