Search code examples
ruby-on-railsrubymongoid

Rails 6 Upgrade Not Loading Mongoid From a Concern


We're upgrading our app from Ruby 2.7 to 3.0 and Rails 5.2 to 6.0 at the same time (I started with just Ruby but 3.0 doesn't seem to be compatible with 5.2). It's close but getting an odd error: uninitialized constant UserFields::Boolean (NameError) on start up when we have our user model's fields split off into a concern:

class User
  include Mongoid::Document
  include Mongoid::Timestamps
  #field :force_password_change, type: Boolean, default: false
  include UserFields
  ....
end
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

If I put the Boolean fields directly in the model, it works fine but not when they are in the concern. The error trace:

 ! 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>'

It stems from the routes file since we use Devise and that is using the User model. I'm sure it has to do with the upgrade as the same code has worked for years in 5.2/2.7. Any help is appreciated.


Solution

  • Update: It is likely this has to do with an upgrade to your mongoid gem. I am not certain what version you were previously using but it appears that < 7.3 defined the top level constant ::Boolean which would allow this to resolve appropriately if you just wrote Boolean Source. Newer versions do not include this top level constant.

    Release Notes state:

    ::Boolean Removed

    ...code that is not executed in the context of a class including Mongoid::Document may need to explicitly qualify Boolean references.

    ...Additionally, in Mongoid 7.2 ::Boolean and Mongoid::Boolean were different classes. In Mongoid 7.3 there is only one class which is Mongoid::Boolean.

    Since your concern is evaluated outside of the context of the class that includes Mongoid::Document you will need to explicitly qualify the constant as Mongoid::Boolean.

    e.g.

    module UserFields
      extend ActiveSupport::Concern
    
      included do
        ## Database authenticatable
        field :email,                 type: String,           default: ""
        field :force_password_change, type: Mongoid::Boolean, default: false  
      end
    end