From 75a2ca6e9dca2c36d80478bcc78ac7bd25bf38f7 Mon Sep 17 00:00:00 2001 From: Miles Georgi Date: Thu, 6 Dec 2018 19:37:00 +0000 Subject: [PATCH] HashWithIndifferentAccess#initialize performance improvement Rails 4 -> Rails 5 introduced a #to_hash call in HashWithIndifferentAccess#initialize to guarantee access to the #default and #default_proc methods. This can be a very expensive operation for very large HashWithIndifferentAccess objects. This commit bypasses this #to_hash call if it is already a Hash. --- .../hash_with_indifferent_access.rb | 2 +- .../test/hash_with_indifferent_access_test.rb | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index f1af76019a..aa81ad6250 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -69,7 +69,7 @@ module ActiveSupport super() update(constructor) - hash = constructor.to_hash + hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash self.default = hash.default if hash.default self.default_proc = hash.default_proc if hash.default_proc else diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb index f81e0dc70f..90f7107b5c 100644 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -828,4 +828,32 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_equal 3, hash_wia[:foo] assert_equal 3, hash_wia[:bar] end + + def test_should_copy_the_default_when_converting_non_hash_to_hash_with_indifferent_access + non_hash = Object.new + + def non_hash.to_hash + h = { foo: :bar } + h.default = :baz + h + end + + hash_wia = HashWithIndifferentAccess.new(non_hash) + assert_equal :bar, hash_wia[:foo] + assert_equal :baz, hash_wia[:missing] + end + + def test_should_copy_the_default_proc_when_converting_non_hash_to_hash_with_indifferent_access + non_hash = Object.new + + def non_hash.to_hash + h = { foo: :bar } + h.default_proc = ->(hash, key) { hash[key] = :baz } + h + end + + hash_wia = HashWithIndifferentAccess.new(non_hash) + assert_equal :bar, hash_wia[:foo] + assert_equal :baz, hash_wia[:missing] + end end