summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--CONTRIBUTING.md11
-rw-r--r--Gemfile27
-rwxr-xr-xbin/setup7
-rw-r--r--hashie.gemspec15
-rw-r--r--lib/hashie/extensions/deep_merge.rb17
-rw-r--r--lib/hashie/utils.rb28
-rw-r--r--spec/hashie/extensions/deep_merge_spec.rb53
8 files changed, 135 insertions, 25 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 49b3aad..1b46c63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ scheme are considered to be bugs.
### Fixed
+* [#467](https://github.com/intridea/hashie/pull/467): Fixed `DeepMerge#deep_merge` mutating nested values within the receiver - [@michaelherold](https://github.com/michaelherold).
* Your contribution here.
### Security
@@ -37,6 +38,7 @@ scheme are considered to be bugs.
### Miscellaneous
+* [#981](https://github.com/hashie/hashie/pull/981): Exclude tests from the gem release to reduce installation size and improve installation speed - [@michaelherold](https://github.com/michaelherold).
* Your contribution here.
## [4.0.0] - 2019-10-30
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5b96827..7eb5cc0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,12 +23,19 @@ git pull upstream master
git checkout -b my-feature-branch
```
-#### Bundle Install and Test
+#### Install dependencies
+
+You can use the setup script to install dependencies for the gem and its integration tests.
+
+```
+bin/setup
+```
+
+#### Test
Ensure that you can build the project and run tests.
```
-bundle install
bundle exec rake
```
diff --git a/Gemfile b/Gemfile
index 5001638..ab470f9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,20 +11,25 @@ group :development do
gem 'pry'
gem 'pry-stack_explorer', platforms: %i[ruby_19 ruby_20 ruby_21]
gem 'rubocop', '0.52.1'
+
+ group :test do
+ # ActiveSupport required to test compatibility with ActiveSupport Core Extensions.
+ # rubocop:disable Bundler/DuplicatedGem
+ require File.expand_path('../lib/hashie/extensions/ruby_version', __FILE__)
+ if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >=
+ Hashie::Extensions::RubyVersion.new('2.4.0')
+ gem 'activesupport', '~> 5.x', require: false
+ else
+ gem 'activesupport', '~> 4.x', require: false
+ end
+ # rubocop:enable Bundler/DuplicatedGem
+ gem 'rake'
+ gem 'rspec', '~> 3'
+ gem 'rspec-pending_for', '~> 0.1'
+ end
end
group :test do
- # ActiveSupport required to test compatibility with ActiveSupport Core Extensions.
- # rubocop:disable Bundler/DuplicatedGem
- require File.expand_path('../lib/hashie/extensions/ruby_version', __FILE__)
- if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >=
- Hashie::Extensions::RubyVersion.new('2.4.0')
- gem 'activesupport', '~> 5.x', require: false
- else
- gem 'activesupport', '~> 4.x', require: false
- end
- # rubocop:enable Bundler/DuplicatedGem
gem 'codeclimate-test-reporter', '~> 1.0', require: false
gem 'danger-changelog', '~> 0.1.0', require: false
- gem 'rspec-core', '~> 3.1.7'
end
diff --git a/bin/setup b/bin/setup
index b65ed50..e52c127 100755
--- a/bin/setup
+++ b/bin/setup
@@ -1,7 +1,12 @@
#!/bin/bash
+
set -euo pipefail
IFS=$'\n\t'
bundle install
-# Do any other automated setup that you need to do here
+for dir in spec/integration/*; do
+ pushd "$dir"
+ bundle install
+ popd
+done
diff --git a/hashie.gemspec b/hashie.gemspec
index 250ecf2..dbb438d 100644
--- a/hashie.gemspec
+++ b/hashie.gemspec
@@ -14,10 +14,15 @@ Gem::Specification.new do |gem|
gem.files = %w[.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE README.md UPGRADING.md]
gem.files += %w[Rakefile hashie.gemspec]
gem.files += Dir['lib/**/*.rb']
- gem.files += Dir['spec/**/*.rb']
- gem.test_files = Dir['spec/**/*.rb']
- gem.add_development_dependency 'rake', '< 11'
- gem.add_development_dependency 'rspec', '~> 3.0'
- gem.add_development_dependency 'rspec-pending_for', '~> 0.1'
+ if gem.respond_to?(:metadata)
+ gem.metadata = {
+ 'bug_tracker_uri' => 'https://github.com/hashie/hashie/issues',
+ 'changelog_uri' => 'https://github.com/hashie/hashie/blob/master/CHANGELOG.md',
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/hashie',
+ 'source_code_uri' => 'https://github.com/hashie/hashie'
+ }
+ end
+
+ gem.add_development_dependency 'bundler'
end
diff --git a/lib/hashie/extensions/deep_merge.rb b/lib/hashie/extensions/deep_merge.rb
index 5363904..1890e8f 100644
--- a/lib/hashie/extensions/deep_merge.rb
+++ b/lib/hashie/extensions/deep_merge.rb
@@ -3,7 +3,7 @@ module Hashie
module DeepMerge
# Returns a new hash with +self+ and +other_hash+ merged recursively.
def deep_merge(other_hash, &block)
- copy = dup
+ copy = _deep_dup(self)
copy.extend(Hashie::Extensions::DeepMerge) unless copy.respond_to?(:deep_merge!)
copy.deep_merge!(other_hash, &block)
end
@@ -18,6 +18,21 @@ module Hashie
private
+ def _deep_dup(hash)
+ copy = hash.dup
+
+ copy.each do |key, value|
+ copy[key] =
+ if value.is_a?(::Hash)
+ _deep_dup(value)
+ else
+ Hashie::Utils.safe_dup(value)
+ end
+ end
+
+ copy
+ end
+
def _recursive_merge(hash, other_hash, &block)
other_hash.each do |k, v|
hash[k] =
diff --git a/lib/hashie/utils.rb b/lib/hashie/utils.rb
index d8e05fe..5b55b9a 100644
--- a/lib/hashie/utils.rb
+++ b/lib/hashie/utils.rb
@@ -12,5 +12,33 @@ module Hashie
"defined in #{bound_method.owner}"
end
end
+
+ # Duplicates a value or returns the value when it is not duplicable
+ #
+ # @api public
+ #
+ # @param value [Object] the value to safely duplicate
+ # @return [Object] the duplicated value
+ def self.safe_dup(value)
+ case value
+ when Complex, FalseClass, NilClass, Rational, Method, Symbol, TrueClass, *integer_classes
+ value
+ else
+ value.dup
+ end
+ end
+
+ # Lists the classes Ruby uses for integers
+ #
+ # @api private
+ # @return [Array<Class>]
+ def self.integer_classes
+ @integer_classes ||=
+ if const_defined?(:Fixnum)
+ [Fixnum, Bignum] # rubocop:disable Lint/UnifiedInteger
+ else
+ [Integer]
+ end
+ end
end
end
diff --git a/spec/hashie/extensions/deep_merge_spec.rb b/spec/hashie/extensions/deep_merge_spec.rb
index ab79ff6..4ff6c30 100644
--- a/spec/hashie/extensions/deep_merge_spec.rb
+++ b/spec/hashie/extensions/deep_merge_spec.rb
@@ -14,19 +14,62 @@ describe Hashie::Extensions::DeepMerge do
context 'without &block' do
let(:h1) do
- subject.new.merge(a: 'a', a1: 42, b: 'b', c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } })
+ subject.new.merge(
+ a: 'a',
+ a1: 42,
+ b: 'b',
+ c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } },
+ d: nil,
+ d1: false,
+ d2: true,
+ d3: unbound_method,
+ d4: Complex(1),
+ d5: Rational(1)
+ )
end
let(:h2) { { a: 1, a1: 1, c: { c1: 2, c2: 'c2', c3: { d2: 'd2' } }, e: { e1: 1 } } }
+ let(:unbound_method) { method(:puts) }
let(:expected_hash) do
- { a: 1, a1: 1, b: 'b', c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } }, e: { e1: 1 } }
+ {
+ a: 1,
+ a1: 1,
+ b: 'b',
+ c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } },
+ d: nil,
+ d1: false,
+ d2: true,
+ d3: unbound_method,
+ d4: Complex(1),
+ d5: Rational(1),
+ e: { e1: 1 }
+ }
end
- it 'deep merges two hashes' do
- expect(h1.deep_merge(h2)).to eq expected_hash
+ it 'deep merges two hashes without modifying them' do
+ result = h1.deep_merge(h2)
+
+ expect(result).to eq expected_hash
+ expect(h1).to(
+ eq(
+ a: 'a',
+ a1: 42,
+ b: 'b',
+ c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } },
+ d: nil,
+ d1: false,
+ d2: true,
+ d3: unbound_method,
+ d4: Complex(1),
+ d5: Rational(1)
+ )
+ )
+ expect(h2).to eq(a: 1, a1: 1, c: { c1: 2, c2: 'c2', c3: { d2: 'd2' } }, e: { e1: 1 })
end
it 'deep merges another hash in place via bang method' do
- h1.deep_merge!(h2)
+ result = h1.deep_merge!(h2)
+
+ expect(result).to eq expected_hash
expect(h1).to eq expected_hash
end