Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MONGOID-5844 Fix counting bug in HABTM associations (backport to 8.0) #5953

Merged
merged 1 commit into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions lib/mongoid/association/referenced/has_many/enumerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,28 @@ def respond_to?(name, include_private = false)
#
# @return [ Integer ] The size of the enumerable.
def size
count = (_unloaded ? _unloaded.count : _loaded.count)
if count.zero?
count + _added.count
# If _unloaded is present, then it will match the set of documents
# that belong to this association, which have already been persisted
# to the database. This set of documents must be considered when
# computing the size of the association, along with anything that has
# since been added.
if _unloaded
if _added.any?
# Note that _added may include records that _unloaded already
# matches. This is the case if the association is assigned an array
# of items and some of them were already elements of the association.
#
# we need to thus make sure _unloaded.count excludes any elements
# that already exist in _added.

count = _unloaded.not(:_id.in => _added.values.map(&:id)).count
count + _added.values.count
else
_unloaded.count
end

else
count + _added.values.count { |d| d.new_record? }
_loaded.count + _added.count
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1757,43 +1757,6 @@
end
end

describe "#any?" do

let(:person) do
Person.create!
end

context "when nothing exists on the relation" do

context "when no document is added" do

let!(:sandwich) do
Sandwich.create!
end

it "returns false" do
expect(sandwich.meats.any?).to be false
end
end

context "when the document is destroyed" do

before do
Meat.create!
end

let!(:sandwich) do
Sandwich.create!
end

it "returns false" do
sandwich.destroy
expect(sandwich.meats.any?).to be false
end
end
end
end

context "when documents have been persisted" do

let!(:preference) do
Expand Down Expand Up @@ -3063,6 +3026,34 @@
end
end

# MONGOID-5844
#
# Specifically, this tests the case where the association is
# initialized with a single element (so that Proxy#push does not take
# the `concat` route), which causes `reset_unloaded` to be called, which
# sets the `_unloaded` Criteria object to match only the specific element
# that was given.
#
# The issue now is that when the events list is updated to be both events,
# _unloaded matches one of them already, and the other has previously been
# persisted so `new_record?` won't match it. We need to make sure the
# `#size` logic properly accounts for this case.
context 'when documents have been previously persisted' do
let(:person1) { Person.create! }
let(:person2) { Person.create! }
let(:event1) { Event.create!(administrators: [ person1 ]) }
let(:event2) { Event.create!(administrators: [ person2 ]) }

before do
person1.administrated_events = [ event1, event2 ]
end

it 'returns the number of associated documents [MONGOID-5844]' do
expect(person1.administrated_events.to_a.size).to eq(2)
expect(person1.administrated_events.size).to eq(2)
end
end

context "when documents have not been persisted" do

before do
Expand Down
Loading