1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #33954 from febeling/inconsistent-assignment-has-many-through-33942

Fix handling of duplicates for `replace` on has_many-through
This commit is contained in:
Aaron Patterson 2018-11-20 09:26:55 -08:00 committed by GitHub
commit 023a840f5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 3 deletions

View file

@ -413,9 +413,9 @@ module ActiveRecord
end
def replace_records(new_target, original_target)
delete(target - new_target)
delete(difference(target, new_target))
unless concat(new_target - target)
unless concat(difference(new_target, target))
@target = original_target
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
@ -425,7 +425,7 @@ module ActiveRecord
end
def replace_common_records_in_memory(new_target, original_target)
common_records = new_target & original_target
common_records = intersection(new_target, original_target)
common_records.each do |record|
skip_callbacks = true
replace_on_target(record, @target.index(record), skip_callbacks)

View file

@ -130,6 +130,14 @@ module ActiveRecord
end
saved_successfully
end
def difference(a, b)
a - b
end
def intersection(a, b)
a & b
end
end
end
end

View file

@ -163,6 +163,28 @@ module ActiveRecord
end
end
def difference(a, b)
distribution = distribution(b)
a.reject { |record| mark_occurrence(distribution, record) }
end
def intersection(a, b)
distribution = distribution(b)
a.select { |record| mark_occurrence(distribution, record) }
end
def mark_occurrence(distribution, record)
distribution[record] > 0 && distribution[record] -= 1
end
def distribution(array)
array.each_with_object(Hash.new(0)) do |record, distribution|
distribution[record] += 1
end
end
def through_records_for(record)
attributes = construct_join_attributes(record)
candidates = Array.wrap(through_association.target)

View file

@ -586,6 +586,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_not_includes posts(:welcome).reload.people.reload, people(:michael)
end
def test_replace_association_with_duplicates
post = posts(:thinking)
person = people(:david)
assert_difference "post.people.count", 2 do
post.people = [person]
post.people = [person, person]
end
end
def test_replace_order_is_preserved
posts(:welcome).people.clear
posts(:welcome).people = [people(:david), people(:michael)]