ExportXMLWordPrintableJSON

    • Type: Bug
    • Resolution: Fixed
    • Priority: Major - P3
    • 9.1.2
    • Affects Version/s: 9.1.0
    • Component/s: None
    • None
    • None
    • Ruby Drivers
    • Not Needed
    • None
    • None
    • None
    • None
    • None
    • None

      Description

      Mongoid 9.1.0 raises NoMethodError when a has_and_belongs_to_many association stores an empty foreign-key array and the association is reset before it is enumerated. Mongoid 9.0.9 does not reproduce the issue.

      Mongoid itself can create this data. For example, Team.create!(user_ids: []) stores an explicit empty array.

      Reproduction

      class User
        include Mongoid::Document
      end
      
      class Team
        include Mongoid::Document
        has_and_belongs_to_many :users
      end
      
      team = Team.create!(user_ids: [])
      team.reload
      
      users = team.users
      users.reset
      users.empty?
      users.size
      users.to_a
      

      On Mongoid 9.1.0, calls after reset raise NoMethodError. For example, to_a raises:

      NoMethodError: undefined method `selector` for nil
      

      Expected behaviour

      An explicitly stored empty foreign-key array should represent an empty association. After reset, empty?, size, and enumeration should return the empty result without querying the database.

      Actual behaviour

      HasAndBelongsToMany::Buildable#build treats an empty array as {{ $lookup }} output because {{[].all?

      { |o| o.is_a?(Hash) }

      }} returns true. It returns a plain Array, so HasMany::Enumerable has no unloaded criteria. After reset, empty?, size, or enumeration can call methods on the missing criteria object.

      Root cause

      The array-of-hashes {{ $lookup }} branch was added by MONGOID-5731 in pull request 6081. Mongoid 9.0.9 did not have this branch, so the empty array reached query_criteria instead.

      The same Array target issue also exists for some {{ $lookup }} results, including non-empty has_and_belongs_to_many results and empty has_many results. Those cases predate this regression and are outside this fix. They should be addressed separately in HasMany::Enumerable.

      Related Mongoid tickets

      MONGOID-5030 covers short-circuiting queries whose {{ $in }} value is an empty array. This issue needs an association-specific none criteria because the target's empty? and size methods must avoid a query even when the general short-circuit option is disabled. Proxy count and exists? are outside this guarantee because they build criteria from the stored IDs.
      MONGOID-6000 (recreated from this)

      Suggested fix

      The {{ $lookup }} branch should accept only non-empty arrays of hashes. For an empty foreign-key array, the implementation should pass nil to query_criteria, which returns crit.none. This preserves a criteria-backed target and avoids database queries from the target's empty?, size, and enumeration methods.

      The existing eager-load specs cover the includes/preload path, including empty results without an extra query. The persisted empty-array path needs a separate regression test because it reaches Buildable#build through a different route.

      Environment

      Mongoid 9.1.0

      The application stores explicit empty foreign-key arrays in its association data.

            Assignee:
            Dmitry Rybakov
            Reporter:
            Tatsuhiro Ujihisa (EXT)
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: