-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Major - P3
-
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.
- fixes
-
MONGOID-6000 Mongoid 9.1.0: resetting a has_and_belongs_to_many association with an empty foreign-key array raises NoMethodError
-
- Closed
-