升级至Rails 6后,Concern中无法加载Mongoid的Boolean类型
问题:Rails 6.0 + Ruby 3.0升级后Concern中Boolean常量未定义错误
我们在将应用从Ruby 2.7/Rails 5.2升级到Ruby 3.0/Rails 6.0时(因Ruby 3.0与Rails 5.2不兼容),遇到启动错误:uninitialized constant UserFields::Boolean (NameError)。错误出现在把User模型的字段拆分到UserFields concern中时,直接在User模型里定义Boolean类型字段则正常运行,原代码在旧环境下已稳定运行多年。
代码示例
User模型代码
class User include Mongoid::Document include Mongoid::Timestamps #field :force_password_change, type: Boolean, default: false include UserFields .... end
UserFields Concern代码
module UserFields extend ActiveSupport::Concern included do ## Database authenticatable field :email, :type => String, :default => "" field :force_password_change, type: Boolean, default: false ### <<<< ERROR HERE end end
错误追踪栈
! Unable to load application: NameError: uninitialized constant UserFields::Boolean 2024-01-10 15:22:22 bundler: failed to load command: puma (/app/bundle_cache/ruby/3.0.0/bin/puma) 2024-01-10 15:22:22 /app/app/models/concerns/user_fields.rb:8:in `block in <module:UserFields>': uninitialized constant UserFields::Boolean (NameError) 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/concern.rb:122:in `class_eval' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/concern.rb:122:in `append_features' 2024-01-10 15:22:22 from /app/app/models/user.rb:9:in `include' 2024-01-10 15:22:22 from /app/app/models/user.rb:9:in `<class:User>' 2024-01-10 15:22:22 from /app/app/models/user.rb:4:in `<main>' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/zeitwerk-2.6.12/lib/zeitwerk/kernel.rb:30:in `require' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/inflector/methods.rb:282:in `const_get' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/inflector/methods.rb:282:in `block in constantize' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/inflector/methods.rb:280:in `each' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/inflector/methods.rb:280:in `inject' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/inflector/methods.rb:280:in `constantize' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/activesupport-6.0.6.1/lib/active_support/dependencies/zeitwerk_integration.rb:19:in `constantize' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise.rb:325:in `get' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/mapping.rb:83:in `to' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/mapping.rb:78:in `modules' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/mapping.rb:95:in `routes' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/mapping.rb:162:in `default_used_route' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/mapping.rb:72:in `initialize' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise.rb:361:in `new' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise.rb:361:in `add_mapping' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/rails/routes.rb:243:in `block in devise_for' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/rails/routes.rb:242:in `each' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/devise-4.9.3/lib/devise/rails/routes.rb:242:in `devise_for' 2024-01-10 15:22:22 from /app/config/routes.rb:264:in `block in <main>' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/actionpack-6.0.6.1/lib/action_dispatch/routing/route_set.rb:426:in `instance_exec' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/actionpack-6.0.6.1/lib/action_dispatch/routing/route_set.rb:426:in `eval_block' 2024-01-10 15:22:22 from /app/bundle_cache/ruby/3.0.0/gems/actionpack-6.0.6.1/lib/action_dispatch/routing/route_set.rb:408:in `draw' 2024-01-10 15:22:22 from /app/config/routes.rb:1:in `<main>'
解决方案
方案1:使用根命名空间常量
在Concern里把Boolean替换成::Boolean,强制从根命名空间查找常量:
field :force_password_change, type: ::Boolean, default: false
方案2:显式指定Mongoid类型
直接使用Mongoid::Boolean或者引入Mongoid的类型模块:
# 直接指定Mongoid类型 field :force_password_change, type: Mongoid::Boolean, default: false # 或者在模块顶部引入类型模块 module UserFields extend ActiveSupport::Concern include Mongoid::Fields::Types included do field :force_password_change, type: Boolean, default: false end end
方案3:使用符号替代常量
Mongoid支持用:boolean符号指定字段类型,完全避开常量查找问题:
field :force_password_change, type: :boolean, default: false
错误原因
Rails 6.0默认使用Zeitwerk加载器,常量查找规则比旧版Autoloader更严格。在Concern的included块中,常量会优先在当前模块(UserFields)的命名空间下查找,而旧环境中会自动回退到根命名空间。加上Devise在路由加载时提前触发了User模型的加载,导致常量查找时机与旧环境不同,最终触发错误。
内容的提问来源于stack exchange,提问作者timsegraves
相关产品推荐
相关产品推荐

