Rails控制器引用Sorbet类型触发NameError问题求助
问题原因
1. 命名空间与目录结构不匹配
Rails自动加载遵循「目录对应模块层级」的约定:如果你的类型常量是Types::Services::Session::UserSignUpParamsType,但文件存放在app/sorbet_types/services/session/user_sign_up_params_type.rb,Rails会默认把这个路径映射为Services::Session::UserSignUpParamsType,而非带Types前缀的命名空间。这种不匹配导致Rails无法通过常量名定位到对应的类型文件。
2. 加载时机差异
控制器在Rails启动阶段就会被预加载(尤其是生产环境的eager load模式),但app/sorbet_types可能未被加入eager load路径,导致类型文件在控制器加载时还未被加载。而services目录的文件是按需加载的,当服务被调用时,类型文件可能已经通过其他请求或依赖加载完成,所以不会触发错误。
3. Sorbet与Rails自动加载的冲突
在Sorbet的typed: strict或typed: strong模式下,静态检查阶段会解析常量,但如果Rails的自动加载还未加载对应文件,就会触发运行时的NameError。
解决方案
方案1:修正命名空间与目录结构(推荐)
严格遵循Rails自动加载约定,二选一调整:
- 保留
Types前缀命名空间:将类型文件移动到app/sorbet_types/types/services/session/user_sign_up_params_type.rb,文件内模块定义保持不变:# app/sorbet_types/types/services/session/user_sign_up_params_type.rb module Types module Services module Session class UserSignUpParamsType # 类型定义代码 end end end end - 简化命名空间:将类型文件留在原路径,把常量改为
Services::Session::UserSignUpParamsType,同时在控制器和services中统一使用这个命名。
方案2:配置自动加载与预加载路径
在config/application.rb中把app/sorbet_types加入autoload和eager load路径,确保所有环境下类型文件能被正确加载:
# config/application.rb config.autoload_paths << Rails.root.join('app/sorbet_types') config.eager_load_paths << Rails.root.join('app/sorbet_types')
开发环境下如果不想全局开启eager load,可以在控制器顶部用require_dependency替代require_relative,让Rails管理加载:
# 控制器文件顶部 require_dependency 'types/services/session/user_sign_up_params_type'
方案3:用Sorbet指令手动映射常量
在类型文件顶层添加Sorbet的autoload指令,明确常量与文件的对应关系:
# app/sorbet_types/services/session/user_sign_up_params_type.rb # typed: true module Types module Services module Session autoload :UserSignUpParamsType, __FILE__ class UserSignUpParamsType # 类型定义代码 end end end end
也可以在sig/config.rb中全局配置常量映射:
# sig/config.rb autoload_constant('Types::Services::Session::UserSignUpParamsType', 'app/sorbet_types/services/session/user_sign_up_params_type.rb')
内容的提问来源于stack exchange,提问作者ryanpback

