diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 3d708d91a7c4b..aab39d48ca2b5 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,14 @@ +* `HashWithIndifferentAccess` better respects `#to_hash` on objects it's + given. In particular `#update`, `#merge`, `#replace` all accept objects + which respond to `#to_hash`, even if those objects are not Hashes directly. + + Currently, if `HashWithIndifferentAccess.new` is given a non-Hash (even if + it responds to `#to_hash`) that object is treated as the default value, + rather than the initial keys and value. Changing that could break existing + code, so it will be updated in the next minor version. + + *Peter Jaros* + * Added `Object#presence_in` to simplify value whitelisting. Before: diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 594a4ca93824a..154f546943a69 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -72,6 +72,7 @@ def default(key = nil) end def self.new_from_hash_copying_default(hash) + hash = hash.to_hash new(hash).tap do |new_hash| new_hash.default = hash.default end @@ -125,7 +126,7 @@ def update(other_hash) if other_hash.is_a? HashWithIndifferentAccess super(other_hash) else - other_hash.each_pair do |key, value| + other_hash.to_hash.each_pair do |key, value| if block_given? && key?(key) value = yield(convert_key(key), self[key], value) end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 40c8f03374154..697ec16db9025 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -23,6 +23,16 @@ def nested_under_indifferent_access end end + class HashByConversion + def initialize(hash) + @hash = hash + end + + def to_hash + @hash + end + end + def setup @strings = { 'a' => 1, 'b' => 2 } @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } } @@ -411,6 +421,12 @@ def test_indifferent_update assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 } end + def test_update_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + hash.update HashByConversion.new({ :a => 1 }) + assert_equal hash['a'], 1 + end + def test_indifferent_merging hash = HashWithIndifferentAccess.new hash[:a] = 'failure' @@ -430,6 +446,12 @@ def test_indifferent_merging assert_equal 2, hash['b'] end + def test_merge_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + merged = hash.merge HashByConversion.new({ :a => 1 }) + assert_equal merged['a'], 1 + end + def test_indifferent_replace hash = HashWithIndifferentAccess.new hash[:a] = 42 @@ -442,6 +464,18 @@ def test_indifferent_replace assert_same hash, replaced end + def test_replace_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + hash[:a] = 42 + + replaced = hash.replace(HashByConversion.new(b: 12)) + + assert hash.key?('b') + assert !hash.key?(:a) + assert_equal 12, hash[:b] + assert_same hash, replaced + end + def test_indifferent_merging_with_block hash = HashWithIndifferentAccess.new hash[:a] = 1