From 80a09caedc6fa67f95e78e45e7eb954f6b647b2c Mon Sep 17 00:00:00 2001
From: Eugene Kenny <elkenny@gmail.com>
Date: Sun, 8 Apr 2018 23:06:48 +0100
Subject: 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.
---
 activemodel/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 @@ module ActiveModel
       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 @@ class DirtyTest < ActiveRecord::TestCase
     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
-- 
cgit v1.2.3