You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 03:01:00