From c5abbbae5907beb67e4670503fe53f0967dc5854 Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Fri, 7 Jul 2023 16:25:40 -0400 Subject: [PATCH] Prevent #merge_target_lists from clearing id column on CPK records Previously, when merging persisted and in-memory records, we were using `record[name]` to get the value of the attribute. For records with a composite primary key, the full CPK would be returned, which was incompatible with `#_write_attribute`: we'd attempt to assign an Array to the `id` column, which would result in the column being set to nil. When dealing with a record with a composite primary key, we need to write each part of the primary key individually via `#_write_attribute`. --- .../associations/collection_association.rb | 6 +++++- activerecord/test/cases/associations_test.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 2c88adb7b9..28d1139e21 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -333,7 +333,11 @@ def merge_target_lists(persisted, memory) if mem_record = memory.delete(record) ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save - mem_record.class._attr_readonly).each do |name| - mem_record._write_attribute(name, record[name]) + if name == "id" && mem_record.class.composite_primary_key? + mem_record.class.primary_key.zip(record[name]) { |attr, value| mem_record._write_attribute(attr, value) } + else + mem_record._write_attribute(name, record[name]) + end end mem_record diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 247b15323a..e4b3f19215 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -79,6 +79,16 @@ def test_loading_the_association_target_should_load_most_recent_attributes_for_c assert_equal "Deck", ship.parts[0].name end + def test_loading_cpk_association_when_persisted_and_in_memory_differ + order = Cpk::Order.create!(id: [1, 2], status: "paid") + book = order.books.create!(id: [3, 4], title: "Book") + + Cpk::Book.find(book.id).update_columns(title: "A different title") + order.books.load + + assert_equal [3, 4], book.id + end + def test_include_with_order_works assert_nothing_raised { Account.all.merge!(order: "id", includes: :firm).first } assert_nothing_raised { Account.all.merge!(order: :id, includes: :firm).first }