From 489309af7ffd8b4fe02bd839924b319cba8aa318 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Fri, 5 Jun 2015 18:48:09 +0200 Subject: [PATCH] Fix bug (#303) preventing use of deep_merge/stringify_keys/symbolize_keys on extended singleton objects. --- CHANGELOG.md | 1 + lib/hashie/extensions/deep_merge.rb | 4 +++- lib/hashie/extensions/stringify_keys.rb | 11 +++++---- lib/hashie/extensions/symbolize_keys.rb | 13 +++++++---- spec/hashie/extensions/deep_merge_spec.rb | 20 ++++++++++++++++ spec/hashie/extensions/stringify_keys_spec.rb | 23 +++++++++++++++++++ spec/hashie/extensions/symbolize_keys_spec.rb | 23 +++++++++++++++++++ 7 files changed, 85 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5740cf9..81d2289a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Next Release * Your contribution here. +* [#304](https://github.com/intridea/hashie/pull/304): Ensured compatibility of `Hash` extensions with singleton objects - [@regexident](https://github.com/regexident). ## 3.4.2 (6/2/2015) diff --git a/lib/hashie/extensions/deep_merge.rb b/lib/hashie/extensions/deep_merge.rb index d7222a3e..df0887d5 100644 --- a/lib/hashie/extensions/deep_merge.rb +++ b/lib/hashie/extensions/deep_merge.rb @@ -3,7 +3,9 @@ module Extensions module DeepMerge # Returns a new hash with +self+ and +other_hash+ merged recursively. def deep_merge(other_hash, &block) - dup.deep_merge!(other_hash, &block) + copy = dup + copy.extend(Hashie::Extensions::DeepMerge) unless copy.respond_to?(:deep_merge!) + copy.deep_merge!(other_hash, &block) end # Returns a new hash with +self+ and +other_hash+ merged recursively. diff --git a/lib/hashie/extensions/stringify_keys.rb b/lib/hashie/extensions/stringify_keys.rb index 13d50bce..41b0fe35 100644 --- a/lib/hashie/extensions/stringify_keys.rb +++ b/lib/hashie/extensions/stringify_keys.rb @@ -15,7 +15,7 @@ def stringify_keys! # Return a new hash with all keys converted # to strings. def stringify_keys - dup.stringify_keys! + StringifyKeys.stringify_keys(self) end module ClassMethods @@ -25,7 +25,7 @@ module ClassMethods def stringify_keys_recursively!(object) case object when self.class - object.stringify_keys! + stringify_keys!(object) when ::Array object.each do |i| stringify_keys_recursively!(i) @@ -43,6 +43,7 @@ def stringify_keys_recursively!(object) # test.stringify_keys! # test # => {'abc' => 'def'} def stringify_keys!(hash) + hash.extend(Hashie::Extensions::StringifyKeys) unless hash.respond_to?(:stringify_keys!) hash.keys.each do |k| stringify_keys_recursively!(hash[k]) hash[k.to_s] = hash.delete(k) @@ -54,8 +55,10 @@ def stringify_keys!(hash) # to strings. # @param [::Hash] hash def stringify_keys(hash) - hash.dup.tap do | new_hash | - stringify_keys! new_hash + copy = hash.dup + copy.extend(Hashie::Extensions::StringifyKeys) unless copy.respond_to?(:stringify_keys!) + copy.tap do |new_hash| + stringify_keys!(new_hash) end end end diff --git a/lib/hashie/extensions/symbolize_keys.rb b/lib/hashie/extensions/symbolize_keys.rb index 12d0669e..274889c1 100644 --- a/lib/hashie/extensions/symbolize_keys.rb +++ b/lib/hashie/extensions/symbolize_keys.rb @@ -15,7 +15,7 @@ def symbolize_keys! # Return a new hash with all keys converted # to symbols. def symbolize_keys - dup.symbolize_keys! + SymbolizeKeys.symbolize_keys(self) end module ClassMethods @@ -23,9 +23,9 @@ module ClassMethods # hashes and arrays. # @api private def symbolize_keys_recursively!(object) - object.symbolize_keys! if object.respond_to? :symbolize_keys! - case object + when self.class + symbolize_keys!(object) when ::Array object.each do |i| symbolize_keys_recursively!(i) @@ -43,6 +43,7 @@ def symbolize_keys_recursively!(object) # Hashie.symbolize_keys! test # test # => {:abc => 'def'} def symbolize_keys!(hash) + hash.extend(Hashie::Extensions::SymbolizeKeys) unless hash.respond_to?(:symbolize_keys!) hash.keys.each do |k| symbolize_keys_recursively!(hash[k]) hash[k.to_sym] = hash.delete(k) @@ -54,8 +55,10 @@ def symbolize_keys!(hash) # to symbols. # @param [::Hash] hash def symbolize_keys(hash) - hash.dup.tap do | new_hash | - symbolize_keys! new_hash + copy = hash.dup + copy.extend(Hashie::Extensions::SymbolizeKeys) unless copy.respond_to?(:symbolize_keys!) + copy.tap do |new_hash| + symbolize_keys!(new_hash) end end end diff --git a/spec/hashie/extensions/deep_merge_spec.rb b/spec/hashie/extensions/deep_merge_spec.rb index cd685233..0815c15a 100644 --- a/spec/hashie/extensions/deep_merge_spec.rb +++ b/spec/hashie/extensions/deep_merge_spec.rb @@ -42,4 +42,24 @@ class DeepMergeHash < Hash expect(h1).to eq expected_hash end end + + context 'from extended object' do + subject { Hash } + let(:h1) { subject.new.merge(a: 100, c: { c1: 100 }).extend(Hashie::Extensions::DeepMerge) } + let(:h2) { { b: 250, c: { c1: 200 } } } + let(:expected_hash) { { a: 100, b: 250, c: { c1: 200 } } } + + it 'does not raise error' do + expect { h1.deep_merge(h2) } .not_to raise_error + end + + it 'deep merges two hashes' do + expect(h1.deep_merge(h2)).to eq expected_hash + end + + it 'deep merges another hash in place via bang method' do + h1.deep_merge!(h2) + expect(h1).to eq expected_hash + end + end end diff --git a/spec/hashie/extensions/stringify_keys_spec.rb b/spec/hashie/extensions/stringify_keys_spec.rb index b0f8aa6b..f5f78d83 100644 --- a/spec/hashie/extensions/stringify_keys_spec.rb +++ b/spec/hashie/extensions/stringify_keys_spec.rb @@ -80,6 +80,29 @@ def invoke(method) include_examples 'stringify_keys!' end end + + context 'singleton methods' do + subject { Hash } + let(:object) { subject.new.merge(a: 1, b: { c: 2 }).extend(Hashie::Extensions::StringifyKeys) } + let(:expected_hash) { { 'a' => 1, 'b' => { 'c' => 2 } } } + + describe '.stringify_keys' do + it 'does not raise error' do + expect { object.stringify_keys } .not_to raise_error + end + it 'produces expected stringified hash' do + expect(object.stringify_keys).to eq(expected_hash) + end + end + describe '.stringify_keys!' do + it 'does not raise error' do + expect { object.stringify_keys! } .not_to raise_error + end + it 'produces expected stringified hash' do + expect(object.stringify_keys!).to eq(expected_hash) + end + end + end end describe Hashie do diff --git a/spec/hashie/extensions/symbolize_keys_spec.rb b/spec/hashie/extensions/symbolize_keys_spec.rb index 862117bd..b62fbe41 100644 --- a/spec/hashie/extensions/symbolize_keys_spec.rb +++ b/spec/hashie/extensions/symbolize_keys_spec.rb @@ -85,6 +85,29 @@ def invoke(method) include_examples 'symbolize_keys!' end end + + context 'singleton methods' do + subject { Hash } + let(:object) { subject.new.merge('a' => 1, 'b' => { 'c' => 2 }).extend(Hashie::Extensions::SymbolizeKeys) } + let(:expected_hash) { { a: 1, b: { c: 2 } } } + + describe '.symbolize_keys' do + it 'does not raise error' do + expect { object.symbolize_keys }.not_to raise_error + end + it 'produces expected symbolized hash' do + expect(object.symbolize_keys).to eq(expected_hash) + end + end + describe '.symbolize_keys!' do + it 'does not raise error' do + expect { object.symbolize_keys! }.not_to raise_error + end + it 'produces expected symbolized hash' do + expect(object.symbolize_keys!).to eq(expected_hash) + end + end + end end describe Hashie do