Fix de-duplication of unsaved records for `ActiveRecord::Associations::CollectionProxy#<<`

The existing process was attempting to detect duplicates by storing added records in `Set.new`.
When a record is added to `Set.new`, the identity is calculated using `ActiveRecord::Core#hash`, but this value changes before and after saving, so duplicates were not detected before and after saving even for the same object.

This PR fixed the problem by using the `#object_id` of the record to detect duplicates.
Note that when storing a new object obtained by `ActiveRecord::Base.find` etc., duplicates are not eliminated because the `#object_id` is different. This is the same behavior as the current `ActiveRecord::Associations::CollectionProxy#<<`.
This commit is contained in:
alpaca-tc 2023-07-05 17:15:41 +09:00
parent fe6b96afd1
commit d413d36de2
2 changed files with 21 additions and 1 deletions

View File

@ -87,7 +87,7 @@ module ActiveRecord
def reset
super
@target = []
@replaced_or_added_targets = Set.new
@replaced_or_added_targets = Set.new.compare_by_identity
@association_ids = nil
end

View File

@ -802,6 +802,26 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
end
def test_with_has_many_inversing_does_not_add_unsaved_duplicate_records_when_collection_is_loaded
with_has_many_inversing(Interest) do
human = Human.create!
human.interests.load
interest = Interest.new(human: human)
human.interests << interest
assert_equal 1, human.interests.size
end
end
def test_with_has_many_inversing_does_not_add_saved_duplicate_records_when_collection_is_loaded
with_has_many_inversing(Interest) do
human = Human.create!
human.interests.load
interest = Interest.create!(human: human)
human.interests << interest
assert_equal 1, human.interests.size
end
end
def test_recursive_model_has_many_inversing
with_has_many_inversing do
main = Branch.create!