hash-deep-merge 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --drb
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Add dependencies required to use your gem here.
4
+ # Example:
5
+ # gem "activesupport", ">= 2.3.5"
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem 'rspec' #, "~> 2.3.0"
11
+ gem 'bundler' #, "~> 1.0.0"
12
+ gem 'jeweler' #, "~> 1.6.0"
13
+ gem 'rcov' #, ">= 0"
14
+ end
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.0)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.0)
11
+ rcov (0.9.9)
12
+ rspec (2.6.0)
13
+ rspec-core (~> 2.6.0)
14
+ rspec-expectations (~> 2.6.0)
15
+ rspec-mocks (~> 2.6.0)
16
+ rspec-core (2.6.2)
17
+ rspec-expectations (2.6.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.6.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler
26
+ jeweler
27
+ rcov
28
+ rspec
@@ -0,0 +1,7 @@
1
+ No Copyright
2
+
3
+ The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
4
+
5
+ You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
6
+
7
+ http://creativecommons.org/publicdomain/zero/1.0/
@@ -0,0 +1,59 @@
1
+ = hash-deep-merge
2
+
3
+ == Introduction
4
+ This little gem add a method to the Hash class, allowing to merge hashes containing other hashes. (This is known as "deep merge" or "recursive merge".)
5
+
6
+ You (may) already know the standard hash merge :
7
+ h1 = { 'a' => 1, 'b' => 2, 'c' => 3}
8
+ h2 = { 'b' => 33, 'd' => 42 }
9
+ h1.merge(h2)
10
+ -> { 'a' => 1, 'b' => 33, 'c' => 3, 'd' => 42 }
11
+
12
+ But now, what if the hash contains other hashes ?
13
+ h1 = { :option1 => true, :option2 => false, :option_group1 => { :sub_option1 => 23, :sub_option2 => "hey !" } }
14
+ h2 = { :option2 => true, :option3 => 27, :option_group1 => { :sub_option1 => 18 } }
15
+ traditional merge doesn't handle that... Here come deep_merge !
16
+ h1.deep_merge(h2)
17
+ -> { :option1 => true, :option2 => true, :option_group1 => { :sub_option1 => 18, :sub_option2 => "hey !" }, :option3 => 27 }
18
+
19
+ Of course, a deep_merge! (note the !) is available, like merge!
20
+
21
+ This should be included in Ruby, but there are some discussions about extra features that prevent it from going forward, cf. http://www.misuse.org/science/2008/05/19/deep_merge-ruby-recursive-merging-for-hashes/
22
+
23
+ The version I give you is the simplest one, doing what you need 99% of the time.
24
+
25
+ There are plenty of code snippets for deep merge :
26
+ * http://snippets.dzone.com/posts/show/4706
27
+ * http://rexchung.com/2007/02/01/hash-recursive-merge-in-ruby/
28
+ * https://gist.github.com/6391
29
+
30
+ But they were not packaged it into a gem. It's done now for your convenience (and mine).
31
+
32
+ NOTE : I since found a gem containing a more powerful deep merge, able to not only merge hashes but arrays and strings as well with many options. You may want to look at it :
33
+ https://github.com/peritor/deep_merge
34
+
35
+ == Installation
36
+
37
+ If you use bundler, just throw that in your gemfile :
38
+ gem 'hash-deep-merge'
39
+
40
+ You may also install the gem manually :
41
+ gem install hash-deep-merge
42
+
43
+ Isn't it so easy ?
44
+
45
+ == Contributing to hash-deep-merge
46
+
47
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
48
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
49
+ * Fork the project
50
+ * Start a feature/bugfix branch
51
+ * Commit and push until you are happy with your contribution
52
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
53
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
54
+
55
+ == Copyright
56
+
57
+ Copyright (c) 2011 Offirmo. See LICENSE.txt for
58
+ further details.
59
+
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "hash-deep-merge"
18
+ gem.homepage = "http://github.com/Offirmo/hash-deep-merge"
19
+ gem.license = "CC0 1.0"
20
+ gem.summary = %Q{add the deep merge feature to class Hash.}
21
+ gem.description = <<-EOF
22
+ This gem add the "deep merge" feature to class Hash.
23
+ It means that if you want to merge hashes that contains other hashes (and so on...), those sub-hashes will be merged as well.
24
+ This is very handy, for example for merging data taken from YAML files.
25
+ EOF
26
+ gem.email = "offirmo.net@gmail.com"
27
+ gem.authors = ["Offirmo"]
28
+ # dependencies defined in Gemfile
29
+ end
30
+ Jeweler::RubygemsDotOrgTasks.new
31
+
32
+ require 'rspec/core'
33
+ require 'rspec/core/rake_task'
34
+ RSpec::Core::RakeTask.new(:spec) do |spec|
35
+ spec.pattern = FileList['spec/**/*_spec.rb']
36
+ end
37
+
38
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
39
+ spec.pattern = 'spec/**/*_spec.rb'
40
+ spec.rcov = true
41
+ end
42
+
43
+ task :default => :spec
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "hash-deep-merge #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{hash-deep-merge}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Offirmo"]
12
+ s.date = %q{2011-05-23}
13
+ s.description = %q{This gem add the "deep merge" feature to class Hash.
14
+ It means that if you want to merge hashes that contains other hashes (and so on...), those sub-hashes will be merged as well.
15
+ This is very handy, for example for merging data taken from YAML files.
16
+ }
17
+ s.email = %q{offirmo.net@gmail.com}
18
+ s.extra_rdoc_files = [
19
+ "LICENSE.txt",
20
+ "README.rdoc"
21
+ ]
22
+ s.files = [
23
+ ".document",
24
+ ".rspec",
25
+ "Gemfile",
26
+ "Gemfile.lock",
27
+ "LICENSE.txt",
28
+ "README.rdoc",
29
+ "Rakefile",
30
+ "VERSION",
31
+ "hash-deep-merge.gemspec",
32
+ "lib/hash_deep_merge.rb",
33
+ "spec/hash-deep-merge_spec.rb",
34
+ "spec/spec_helper.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/Offirmo/hash-deep-merge}
37
+ s.licenses = ["CC0 1.0"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.6.2}
40
+ s.summary = %q{add the deep merge feature to class Hash.}
41
+
42
+ if s.respond_to? :specification_version then
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
46
+ s.add_development_dependency(%q<rspec>, [">= 0"])
47
+ s.add_development_dependency(%q<bundler>, [">= 0"])
48
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
49
+ s.add_development_dependency(%q<rcov>, [">= 0"])
50
+ else
51
+ s.add_dependency(%q<rspec>, [">= 0"])
52
+ s.add_dependency(%q<bundler>, [">= 0"])
53
+ s.add_dependency(%q<jeweler>, [">= 0"])
54
+ s.add_dependency(%q<rcov>, [">= 0"])
55
+ end
56
+ else
57
+ s.add_dependency(%q<rspec>, [">= 0"])
58
+ s.add_dependency(%q<bundler>, [">= 0"])
59
+ s.add_dependency(%q<jeweler>, [">= 0"])
60
+ s.add_dependency(%q<rcov>, [">= 0"])
61
+ end
62
+ end
63
+
@@ -0,0 +1,47 @@
1
+ class Hash
2
+
3
+ def deep_merge!(specialized_hash)
4
+ return internal_deep_merge!(self, specialized_hash)
5
+ end
6
+
7
+
8
+ def deep_merge(specialized_hash)
9
+ return internal_deep_merge!(Hash.new.replace(self), specialized_hash)
10
+ end
11
+
12
+
13
+ protected
14
+
15
+ # better, recursive, preserving method
16
+ # OK OK this is not the most efficient algorithm,
17
+ # but at last it's *perfectly clear and understandable*
18
+ # so fork and improve if you need 5% more speed, ok ?
19
+ def internal_deep_merge!(source_hash, specialized_hash)
20
+
21
+ #puts "starting deep merge..."
22
+
23
+ specialized_hash.each_pair do |rkey, rval|
24
+ #puts " potential replacing entry : " + rkey.inspect
25
+
26
+ if source_hash.has_key?(rkey) then
27
+ #puts " found potentially conflicting entry for #{rkey.inspect} : #{rval.inspect}, will merge :"
28
+ if rval.is_a?(Hash) and source_hash[rkey].is_a?(Hash) then
29
+ #puts " recursing..."
30
+ internal_deep_merge!(source_hash[rkey], rval)
31
+ elsif rval == source_hash[rkey] then
32
+ #puts " same value, skipping."
33
+ else
34
+ #puts " replacing."
35
+ source_hash[rkey] = rval
36
+ end
37
+ else
38
+ #puts " found new entry #{rkey.inspect}, adding it..."
39
+ source_hash[rkey] = rval
40
+ end
41
+ end
42
+
43
+ #puts "deep merge done."
44
+
45
+ return source_hash
46
+ end
47
+ end
@@ -0,0 +1,458 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "HashDeepMerge" do
4
+
5
+
6
+ ######################################################################
7
+ describe "handling of ordinary hashes" do
8
+
9
+ before(:each) do
10
+ # When hashes don't contain other hashes, deep_merge works the same as merge.
11
+ @hash1 = { 3 => 2, :test => 1, 'toto' => 'titi', :foo => :bar }
12
+ @hash2 = { 3 => '42', :test => 2, 'toto' => 'titi', :fooz => :barz }
13
+
14
+ # Expected results, computed manually.
15
+ @expected_result_1to2 = { 3 => '42', :test => 2, 'toto' => 'titi', :foo => :bar, :fooz => :barz }
16
+ @expected_result_2to1 = { 3 => 2, :test => 1, 'toto' => 'titi', :fooz => :barz, :foo => :bar }
17
+
18
+ # We store copie to check for modifications,
19
+ # because some functions are expected to change the values and some not.
20
+ @hash1_copy = Hash.new.replace(@hash1)
21
+ @hash2_copy = Hash.new.replace(@hash2)
22
+
23
+ # This is not really a test, it's just to remember prerequisites for following tests.
24
+ # (If we want to test no modifications, we just have to merge a hash with itself)
25
+ @expected_result_1to2.should_not == @hash1
26
+ @expected_result_1to2.should_not == @hash2
27
+ @expected_result_2to1.should_not == @hash1
28
+ @expected_result_2to1.should_not == @hash2
29
+ end
30
+
31
+ ######################## MERGE ########################
32
+ describe "merge function" do
33
+ it "should behave the same (exact identity case)" do
34
+
35
+ expected_result = @hash1_copy
36
+
37
+ # first we do it with the ordinary "merge" function, to check.
38
+ result = @hash1.merge(@hash1) # merge to itself
39
+ @hash1.should == @hash1_copy # should not have been modified
40
+ @hash2.should == @hash2_copy # should not have been modified
41
+ result.should == expected_result
42
+
43
+ # Now we do it with deep_merge
44
+ result = @hash1.deep_merge(@hash1)
45
+ @hash1.should == @hash1_copy # should not have been modified
46
+ @hash2.should == @hash2_copy # should not have been modified
47
+ result.should == expected_result
48
+ end
49
+
50
+ it "should behave the same (shuffled identity case)" do
51
+ # really needed ?
52
+ pending
53
+ end
54
+
55
+ it "should behave the same (different case)" do
56
+ expected_result = @expected_result_1to2
57
+
58
+ # first we do it with the ordinary "merge" function, to check.
59
+ result = @hash1.merge(@hash2)
60
+ @hash1.should == @hash1_copy # should not have been modified
61
+ @hash2.should == @hash2_copy # should not have been modified
62
+ result.should == expected_result
63
+
64
+ # Now we do it with deep_merge
65
+ result = @hash1.deep_merge(@hash2)
66
+ @hash1.should == @hash1_copy # should not have been modified
67
+ @hash2.should == @hash2_copy # should not have been modified
68
+ result.should == expected_result
69
+ end
70
+
71
+ it "should behave the same (different case reversed)" do
72
+ expected_result = @expected_result_2to1
73
+
74
+ # first we do it with the ordinary "merge" function, to check.
75
+ result = @hash2.merge(@hash1)
76
+ @hash1.should == @hash1_copy # should not have been modified
77
+ @hash2.should == @hash2_copy # should not have been modified
78
+ result.should == expected_result
79
+
80
+ # Now we do it with deep_merge
81
+ result = @hash2.deep_merge(@hash1)
82
+ @hash1.should == @hash1_copy # should not have been modified
83
+ @hash2.should == @hash2_copy # should not have been modified
84
+ result.should == expected_result
85
+ end
86
+ end # merge
87
+
88
+ ######################## MERGE!!! ########################
89
+ describe "merge!" do
90
+ it "should behave the same (exact identity case)" do
91
+
92
+ expected_result = @hash1_copy
93
+
94
+ # first we do it with the ordinary "merge" function, to check.
95
+ @hash1.merge!(@hash1) # merge to itself
96
+ result = @hash1
97
+ @hash1.should == @hash1_copy # should be equal
98
+ @hash2.should == @hash2_copy # should not have been modified
99
+ result.should == expected_result
100
+
101
+ # restore @hash1
102
+ @hash1 = Hash.new.replace(@hash1_copy)
103
+ @hash1.should == @hash1_copy # should have been restored
104
+
105
+ # Now we do it with deep_merge
106
+ @hash1.deep_merge!(@hash1)
107
+ result = @hash1
108
+ @hash1.should == @hash1_copy # should be equal
109
+ @hash2.should == @hash2_copy # should not have been modified
110
+ result.should == expected_result
111
+ end
112
+
113
+ it "should behave the same (shuffled identity case)" do
114
+ # really needed ?
115
+ pending
116
+ end
117
+
118
+ it "should behave the same (different case)" do
119
+ expected_result = @expected_result_1to2
120
+
121
+ # first we do it with the ordinary "merge!" function, to check.
122
+ @hash1.merge!(@hash2) # XXX with ! XXX
123
+ result = @hash1
124
+ @hash1.should_not == @hash1_copy # *should* have been modified
125
+ @hash2.should == @hash2_copy # should not have been modified
126
+ result.should == expected_result
127
+
128
+ # restore @hash1
129
+ @hash1 = Hash.new.replace(@hash1_copy)
130
+ @hash1.should == @hash1_copy # should have been restored
131
+
132
+ # Now we do it with deep_merge
133
+ result = @hash1.deep_merge!(@hash2) # XXX with ! XXX
134
+ @hash1.should_not == @hash1_copy # *should* have been modified
135
+ @hash2.should == @hash2_copy # should not have been modified
136
+ result.should == expected_result
137
+ end
138
+
139
+ it "should behave the same (different case reversed)" do
140
+ expected_result = @expected_result_2to1
141
+
142
+ # first we do it with the ordinary "merge!" function, to check.
143
+ @hash2.merge!(@hash1)
144
+ result = @hash2
145
+ @hash1.should == @hash1_copy # should not have been modified
146
+ @hash2.should_not == @hash2_copy # *should* have been modified
147
+ result.should == expected_result
148
+
149
+ # restore @hash2
150
+ @hash2 = Hash.new.replace(@hash2_copy)
151
+ @hash2.should == @hash2_copy # should not have been modified
152
+
153
+ # Now we do it with deep_merge
154
+ @hash2.deep_merge!(@hash1)
155
+ result = @hash2
156
+ @hash1.should == @hash1_copy # should not have been modified
157
+ @hash2.should_not == @hash2_copy # *should* have been modified
158
+ result.should == expected_result
159
+ end
160
+ end # merge!
161
+ end # handling of ordinary hashes
162
+
163
+
164
+
165
+ ######################################################################
166
+ describe "handling of hashes containing hashes" do
167
+
168
+ before(:each) do
169
+ # same as before, but with sub hashes
170
+ @hash1 = { 3 => 2, :test => 1, 'toto' => 'titi', :foo => :bar,
171
+ 'sub hash 1' => {
172
+ 3 => 2, :test => 1, 'toto' => 'titi', :foo => :bar },
173
+ 'sub hash 2' => {
174
+ 'hello' => 'world' },
175
+ }
176
+ @hash2 = { 3 => '42', :test => 2, 'toto' => 'titi', :fooz => :barz,
177
+ 'sub hash 1' => {
178
+ 3 => '42', :test => 2, 'toto' => 'titi', :fooz => :barz },
179
+ 'sub hash 3' => {
180
+ 'hello' => 'world' },
181
+ }
182
+
183
+ # Expected results, computed manually.
184
+ @expected_result_1to2 = { 3 => '42',
185
+ :test => 2,
186
+ 'toto' => 'titi',
187
+ :foo => :bar,
188
+ 'sub hash 1' => {
189
+ 3 => '42', :test => 2, 'toto' => 'titi', :foo => :bar, :fooz => :barz},
190
+ 'sub hash 2' => {
191
+ 'hello' => 'world' },
192
+ 'sub hash 3' => {
193
+ 'hello' => 'world' },
194
+ :fooz => :barz,
195
+ }
196
+ @expected_result_2to1 = { 3 => 2,
197
+ :test => 1,
198
+ 'toto' => 'titi',
199
+ :fooz => :barz,
200
+ 'sub hash 1' => {
201
+ 3 => 2, :test => 1, 'toto' => 'titi', :fooz => :barz, :foo => :bar},
202
+ 'sub hash 3' => {
203
+ 'hello' => 'world' },
204
+ 'sub hash 2' => {
205
+ 'hello' => 'world' },
206
+ :foo => :bar,
207
+ }
208
+
209
+ # We store copie to check for modifications,
210
+ # because some functions are expected to change the values and some not.
211
+ @hash1_copy = Hash.new.replace(@hash1)
212
+ @hash2_copy = Hash.new.replace(@hash2)
213
+
214
+ # This is not really a test, it's just to remember prerequisites for following tests.
215
+ # (If we want to test no modifications, we just have to merge a hash with itself)
216
+ @expected_result_1to2.should_not == @hash1
217
+ @expected_result_1to2.should_not == @hash2
218
+ @expected_result_2to1.should_not == @hash1
219
+ @expected_result_2to1.should_not == @hash2
220
+ end
221
+
222
+ ######################## MERGE ########################
223
+ describe "merge" do
224
+
225
+ it "should work (exact identity case)" do
226
+
227
+ expected_result = @hash1_copy
228
+ result = @hash1.deep_merge(@hash1)
229
+
230
+ @hash1.should == @hash1_copy # should be equal
231
+ @hash2.should == @hash2_copy # should not have been modified
232
+ result.should == expected_result
233
+ end
234
+
235
+ it "should work (shuffled identity case)" do
236
+ # really needed ?
237
+ pending
238
+ end
239
+
240
+ it "should work (different case)" do
241
+
242
+ expected_result = @expected_result_1to2
243
+ result = @hash1.deep_merge(@hash2)
244
+
245
+ @hash1.should == @hash1_copy # should not have been modified
246
+ @hash2.should == @hash2_copy # should not have been modified
247
+ result.should == expected_result
248
+ result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
249
+ result['sub hash 1'].should == expected_result['sub hash 1']
250
+ result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
251
+ result['sub hash 2'].should == expected_result['sub hash 2']
252
+ result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
253
+ result['sub hash 3'].should == expected_result['sub hash 3']
254
+ end
255
+
256
+ it "should work (different case reversed)" do
257
+
258
+ expected_result = @expected_result_2to1
259
+ result = @hash2.deep_merge(@hash1)
260
+
261
+ @hash1.should == @hash1_copy # should not have been modified
262
+ @hash2.should == @hash2_copy # should not have been modified
263
+ result.should == expected_result
264
+ result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
265
+ result['sub hash 1'].should == expected_result['sub hash 1']
266
+ result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
267
+ result['sub hash 2'].should == expected_result['sub hash 2']
268
+ result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
269
+ result['sub hash 3'].should == expected_result['sub hash 3']
270
+ end
271
+
272
+ end # merge
273
+
274
+ ######################## MERGE!!! ########################
275
+ describe "merge!" do
276
+
277
+ it "should work (exact identity case)" do
278
+
279
+ expected_result = @hash1_copy
280
+ @hash1.deep_merge!(@hash1)
281
+ result = @hash1
282
+
283
+ @hash1.should == @hash1_copy # should be equal
284
+ @hash2.should == @hash2_copy # should not have been modified
285
+ result.should == expected_result
286
+ end
287
+
288
+ it "should work (shuffled identity case)" do
289
+ # really needed ?
290
+ pending
291
+ end
292
+
293
+ it "should work (different case)" do
294
+
295
+ expected_result = @expected_result_1to2
296
+ @hash1.deep_merge!(@hash2)
297
+ result = @hash1
298
+
299
+ @hash1.should_not == @hash1_copy # *should* have been modified
300
+ @hash2.should == @hash2_copy # should not have been modified
301
+ result.should == expected_result
302
+ result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
303
+ result['sub hash 1'].should == expected_result['sub hash 1']
304
+ result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
305
+ result['sub hash 2'].should == expected_result['sub hash 2']
306
+ result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
307
+ result['sub hash 3'].should == expected_result['sub hash 3']
308
+ end
309
+
310
+ it "should work (different case reversed)" do
311
+
312
+ expected_result = @expected_result_2to1
313
+ @hash2.deep_merge!(@hash1)
314
+ result = @hash2
315
+
316
+ @hash1.should == @hash1_copy # should not have been modified
317
+ @hash2.should_not == @hash2_copy # *should* have been modified
318
+ result.should == expected_result
319
+ result['sub hash 1'].should have(expected_result['sub hash 1'].length).entries
320
+ result['sub hash 1'].should == expected_result['sub hash 1']
321
+ result['sub hash 2'].should have(expected_result['sub hash 2'].length).entries
322
+ result['sub hash 2'].should == expected_result['sub hash 2']
323
+ result['sub hash 3'].should have(expected_result['sub hash 3'].length).entries
324
+ result['sub hash 3'].should == expected_result['sub hash 3']
325
+ end
326
+
327
+ end # merge
328
+ end # hash with sub hashes
329
+
330
+ ######################################################################
331
+ describe "handling of hashes containing hashes containing hashes" do
332
+ before(:each) do
333
+ # same as before, but with sub sub hashes
334
+ # things start to get complicated, isn't it ?
335
+ @hash1 = {
336
+ 3 => 2,
337
+ :test => 1,
338
+ 'toto' => 'titi',
339
+ :foo => :bar,
340
+ 'sub hash 1' => {
341
+ 3 => 2,
342
+ :test => 1,
343
+ 'toto' => 'titi',
344
+ :foo => :bar,
345
+ 'sub sub hash 1' => {
346
+ 3 => '42',
347
+ :test => 2,
348
+ 'toto' => 'titi',
349
+ :fooz => :barz
350
+ },
351
+ },
352
+ 'sub hash 2' => {
353
+ 'hello' => 'world'
354
+ },
355
+ }
356
+ @hash2 = {
357
+ 3 => '42',
358
+ :test => 2,
359
+ 'toto' => 'titi',
360
+ :fooz => :barz,
361
+ 'sub hash 1' => {
362
+ 3 => '42',
363
+ :test => 2,
364
+ 'toto' => 'titi',
365
+ :fooz => :barz,
366
+ 'sub sub hash 1' => {
367
+ 3 => 2,
368
+ :test => 1,
369
+ 'toto' => 'titi',
370
+ :foo => :bar
371
+ },
372
+ },
373
+ 'sub hash 2' => {
374
+ 'hello' => 'worldy'
375
+ },
376
+ }
377
+
378
+ # Expected results, computed manually.
379
+ @expected_result_1to2 = {
380
+ 3 => '42',
381
+ :test => 2,
382
+ 'toto' => 'titi',
383
+ :foo => :bar,
384
+ 'sub hash 1' => {
385
+ 3 => '42',
386
+ :test => 2,
387
+ 'toto' => 'titi',
388
+ :foo => :bar,
389
+ 'sub sub hash 1' => {
390
+ 3 => 2,
391
+ :test => 1,
392
+ 'toto' => 'titi',
393
+ :fooz => :barz,
394
+ :foo => :bar
395
+ },
396
+ :fooz => :barz
397
+ },
398
+ 'sub hash 2' => {
399
+ 'hello' => 'worldy'
400
+ },
401
+ :fooz => :barz,
402
+ }
403
+
404
+ # Gaaah ! My head burns !
405
+
406
+ # We store copie to check for modifications,
407
+ # because some functions are expected to change the values and some not.
408
+ @hash1_copy = Hash.new.replace(@hash1)
409
+ @hash2_copy = Hash.new.replace(@hash2)
410
+
411
+ # This is not really a test, it's just to remember prerequisites for following tests.
412
+ # (If we want to test no modifications, we just have to merge a hash with itself)
413
+ @expected_result_1to2.should_not == @hash1
414
+ @expected_result_1to2.should_not == @hash2
415
+ end
416
+
417
+ ######################## MERGE ########################
418
+ describe "merge" do
419
+
420
+ it "should work (exact identity case)" do
421
+
422
+ expected_result = @hash1_copy
423
+ @hash1.deep_merge!(@hash1)
424
+ result = @hash1
425
+
426
+ @hash1.should == @hash1_copy # should be equal
427
+ @hash2.should == @hash2_copy # should not have been modified
428
+ result.should == expected_result
429
+ end
430
+
431
+ it "should work (shuffled identity case)" do
432
+ # really needed ?
433
+ pending
434
+ end
435
+
436
+ it "should work (different case)" do
437
+
438
+ expected_result = @expected_result_1to2
439
+ @hash1.deep_merge!(@hash2)
440
+ result = @hash1
441
+
442
+ @hash1.should_not == @hash1_copy # *should* have been modified
443
+ @hash2.should == @hash2_copy # should not have been modified
444
+ result.should == expected_result
445
+ result['sub hash 1'].should == expected_result['sub hash 1']
446
+ result['sub hash 1']['sub sub hash 1'].should == expected_result['sub hash 1']['sub sub hash 1']
447
+ result['sub hash 2'].should == expected_result['sub hash 2']
448
+ end
449
+ end # merge
450
+ end
451
+
452
+ ######################################################################
453
+ describe "handling of hashes containing hashes containing hashes containing hashes" do
454
+ # herm... maybe we'll stop here ;-)
455
+ # I'm mad enough already...
456
+ end
457
+
458
+ end # describe "HashDeepMerge"
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'hash_deep_merge'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ # I add some display to clearly mark the beginning of the tests
11
+ puts "*\n"*12
12
+
13
+ RSpec.configure do |config|
14
+
15
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hash-deep-merge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Offirmo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-05-23 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &78820430 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *78820430
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ requirement: &78820190 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *78820190
37
+ - !ruby/object:Gem::Dependency
38
+ name: jeweler
39
+ requirement: &78819910 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *78819910
48
+ - !ruby/object:Gem::Dependency
49
+ name: rcov
50
+ requirement: &78819670 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *78819670
59
+ description: ! 'This gem add the "deep merge" feature to class Hash.
60
+
61
+ It means that if you want to merge hashes that contains other hashes (and so on...),
62
+ those sub-hashes will be merged as well.
63
+
64
+ This is very handy, for example for merging data taken from YAML files.
65
+
66
+ '
67
+ email: offirmo.net@gmail.com
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files:
71
+ - LICENSE.txt
72
+ - README.rdoc
73
+ files:
74
+ - .document
75
+ - .rspec
76
+ - Gemfile
77
+ - Gemfile.lock
78
+ - LICENSE.txt
79
+ - README.rdoc
80
+ - Rakefile
81
+ - VERSION
82
+ - hash-deep-merge.gemspec
83
+ - lib/hash_deep_merge.rb
84
+ - spec/hash-deep-merge_spec.rb
85
+ - spec/spec_helper.rb
86
+ has_rdoc: true
87
+ homepage: http://github.com/Offirmo/hash-deep-merge
88
+ licenses:
89
+ - CC0 1.0
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ segments:
101
+ - 0
102
+ hash: 814986959
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 1.6.2
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: add the deep merge feature to class Hash.
115
+ test_files: []