From 80a09caedc6fa67f95e78e45e7eb954f6b647b2c Mon Sep 17 00:00:00 2001 From: Eugene Kenny Date: Sun, 8 Apr 2018 23:06:48 +0100 Subject: [PATCH] Prevent changes_to_save from mutating attributes When an array of hashes is added to a `HashWithIndifferentAccess`, the hashes are replaced with HWIAs by mutating the array in place. If an attribute's value is an array of hashes, `changes_to_save` will convert it to an array of HWIAs as a side-effect of adding it to the changes hash. Using `merge!` instead of `[]=` fixes the problem, as `merge!` copies any array values in the provided hash instead of mutating them. --- .../lib/active_model/attribute_mutation_tracker.rb | 2 +- activerecord/test/cases/dirty_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/attribute_mutation_tracker.rb b/activemodel/lib/active_model/attribute_mutation_tracker.rb index 493be5bb88..1759f004a6 100644 --- a/activemodel/lib/active_model/attribute_mutation_tracker.rb +++ b/activemodel/lib/active_model/attribute_mutation_tracker.rb @@ -23,7 +23,7 @@ def changes attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| change = change_to_attribute(attr_name) if change - result[attr_name] = change + result.merge!(attr_name => change) end end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 2f1434b2bc..5c7f70b7a0 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -473,6 +473,14 @@ def test_save_should_not_save_serialized_attribute_with_partial_writes_if_not_pr end end + def test_changes_to_save_should_not_mutate_array_of_hashes + topic = Topic.new(author_name: "Bill", content: [{ a: "a" }]) + + topic.changes_to_save + + assert_equal [{ a: "a" }], topic.content + end + def test_previous_changes # original values should be in previous_changes pirate = Pirate.new