deep_merge 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README +88 -0
  2. data/lib/deep_merge.rb +206 -0
  3. data/test/test_deep_merge.rb +553 -0
  4. metadata +55 -0
data/README ADDED
@@ -0,0 +1,88 @@
1
+ DeepMerge Overview
2
+ ==================
3
+
4
+ Deep Merge is a simple set of utility functions for Hash. It permits
5
+ you to merge elements inside a hash together recursively. The manner
6
+ by which it does this is somewhat arbitrary (since there is no defining
7
+ standard for this) but it should end up being pretty intuitive and do what
8
+ you expect.
9
+
10
+ You can learn a lot more about this by reading the test file. It's pretty
11
+ well documented and has many examples of various merges from very simple
12
+ to pretty complex.
13
+
14
+ The primary need that caused me to write this library is the merging of elements
15
+ coming from HTTP parameters and related stored parameters in session. This lets
16
+ a user build up a set of parameters over time, modifying individual items.
17
+
18
+ Deep Merge Core Documentation
19
+ =============================
20
+ deep_merge! method permits merging of arbitrary child elements. The two top level
21
+ elements must be hashes. These hashes can contain unlimited (to stack limit) levels
22
+ of child elements. These child elements to not have to be of the same types.
23
+ Where child elements are of the same type, deep_merge will attempt to merge them together.
24
+ Where child elements are not of the same type, deep_merge will skip or optionally overwrite
25
+ the destination element with the contents of the source element at that level.
26
+ So if you have two hashes like this:
27
+ source = {:x => [1,2,3], :y => 2}
28
+ dest = {:x => [4,5,'6'], :y => [7,8,9]}
29
+ dest.deep_merge!(source)
30
+ Results: {:x => [1,2,3,4,5,'6'], :y => 2}
31
+ By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
32
+ To avoid this, use "deep_merge" (no bang/exclamation mark)
33
+
34
+ Options:
35
+ Options are specified in the last parameter passed, which should be in hash format:
36
+ hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'})
37
+ :preserve_unmergeables DEFAULT: false
38
+ Set to true to skip any unmergeable elements from source
39
+ :knockout_prefix DEFAULT: nil
40
+ Set to string value to signify prefix which deletes elements from existing element
41
+ :sort_merged_arrays DEFAULT: false
42
+ Set to true to sort all arrays that are merged together
43
+ :unpack_arrays DEFAULT: nil
44
+ Set to string value to run "Array::join" then "String::split" against all arrays
45
+ :merge_debug DEFAULT: false
46
+ Set to true to get console output of merge process for debugging
47
+
48
+ Selected Options Details:
49
+ :knockout_prefix => The purpose of this is to provide a way to remove elements
50
+ from existing Hash by specifying them in a special way in incoming hash
51
+ source = {:x => ['--1', '2']}
52
+ dest = {:x => ['1', '3']}
53
+ dest.ko_deep_merge!(source)
54
+ Results: {:x => ['2','3']}
55
+ Additionally, if the knockout_prefix is passed alone as a string, it will cause
56
+ the entire element to be removed:
57
+ source = {:x => '--'}
58
+ dest = {:x => [1,2,3]}
59
+ dest.ko_deep_merge!(source)
60
+ Results: {:x => ""}
61
+ :unpack_arrays => The purpose of this is to permit compound elements to be passed
62
+ in as strings and to be converted into discrete array elements
63
+ irsource = {:x => ['1,2,3', '4']}
64
+ dest = {:x => ['5','6','7,8']}
65
+ dest.deep_merge!(source, {:unpack_arrays => ','})
66
+ Results: {:x => ['1','2','3','4','5','6','7','8'}
67
+ Why: If receiving data from an HTML form, this makes it easy for a checkbox
68
+ to pass multiple values from within a single HTML element
69
+
70
+ There are many tests for this library - and you can learn more about the features
71
+ and usages of deep_merge! by just browsing the test examples
72
+
73
+
74
+ Simple Example Code
75
+ ===================
76
+
77
+ require 'deep_merge'
78
+ x = {:x => [3,4,5]}
79
+ y = {:x => [1,2,3]}
80
+ y.deep_merge!(x)
81
+ # results: y = {:x => [1,2,3,4,5]}
82
+
83
+ Availablility
84
+ =============
85
+ SVN Repo here: http://trac.misuse.org/science/wiki/DeepMerge
86
+ Contact author: http://www.misuse.org/science
87
+
88
+ Copyright (c) 2008 Steve Midgley, released under the MIT license
@@ -0,0 +1,206 @@
1
+ module DeepMerge
2
+
3
+ class InvalidParameter < StandardError; end
4
+
5
+ DEFAULT_FIELD_KNOCKOUT_PREFIX = '--'
6
+
7
+ module DeepMergeHash
8
+ # ko_hash_merge! will merge and knockout elements prefixed with DEFAULT_FIELD_KNOCKOUT_PREFIX
9
+ def ko_deep_merge!(source, options = {})
10
+ default_opts = {:knockout_prefix => "--", :preserve_unmergeables => false}
11
+ DeepMerge::deep_merge!(source, self, default_opts.merge(options))
12
+ end
13
+
14
+ # deep_merge! will merge and overwrite any unmergeables in destination hash
15
+ def deep_merge!(source, options = {})
16
+ default_opts = {:preserve_unmergeables => false}
17
+ DeepMerge::deep_merge!(source, self, default_opts.merge(options))
18
+ end
19
+
20
+ # deep_merge will merge and skip any unmergeables in destination hash
21
+ def deep_merge(source, options = {})
22
+ default_opts = {:preserve_unmergeables => true}
23
+ DeepMerge::deep_merge!(source, self, default_opts.merge(options))
24
+ end
25
+
26
+ end # DeepMergeHashExt
27
+
28
+ # Deep Merge core documentation.
29
+ # deep_merge! method permits merging of arbitrary child elements. The two top level
30
+ # elements must be hashes. These hashes can contain unlimited (to stack limit) levels
31
+ # of child elements. These child elements to not have to be of the same types.
32
+ # Where child elements are of the same type, deep_merge will attempt to merge them together.
33
+ # Where child elements are not of the same type, deep_merge will skip or optionally overwrite
34
+ # the destination element with the contents of the source element at that level.
35
+ # So if you have two hashes like this:
36
+ # source = {:x => [1,2,3], :y => 2}
37
+ # dest = {:x => [4,5,'6'], :y => [7,8,9]}
38
+ # dest.deep_merge!(source)
39
+ # Results: {:x => [1,2,3,4,5,'6'], :y => 2}
40
+ # By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
41
+ # To avoid this, use "deep_merge" (no bang/exclamation mark)
42
+ #
43
+ # Options:
44
+ # Options are specified in the last parameter passed, which should be in hash format:
45
+ # hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'})
46
+ # :preserve_unmergeables DEFAULT: false
47
+ # Set to true to skip any unmergeable elements from source
48
+ # :knockout_prefix DEFAULT: nil
49
+ # Set to string value to signify prefix which deletes elements from existing element
50
+ # :sort_merged_arrays DEFAULT: false
51
+ # Set to true to sort all arrays that are merged together
52
+ # :unpack_arrays DEFAULT: nil
53
+ # Set to string value to run "Array::join" then "String::split" against all arrays
54
+ # :merge_debug DEFAULT: false
55
+ # Set to true to get console output of merge process for debugging
56
+ #
57
+ # Selected Options Details:
58
+ # :knockout_prefix => The purpose of this is to provide a way to remove elements
59
+ # from existing Hash by specifying them in a special way in incoming hash
60
+ # source = {:x => ['--1', '2']}
61
+ # dest = {:x => ['1', '3']}
62
+ # dest.ko_deep_merge!(source)
63
+ # Results: {:x => ['2','3']}
64
+ # Additionally, if the knockout_prefix is passed alone as a string, it will cause
65
+ # the entire element to be removed:
66
+ # source = {:x => '--'}
67
+ # dest = {:x => [1,2,3]}
68
+ # dest.ko_deep_merge!(source)
69
+ # Results: {:x => ""}
70
+ # :unpack_arrays => The purpose of this is to permit compound elements to be passed
71
+ # in as strings and to be converted into discrete array elements
72
+ # irsource = {:x => ['1,2,3', '4']}
73
+ # dest = {:x => ['5','6','7,8']}
74
+ # dest.deep_merge!(source, {:unpack_arrays => ','})
75
+ # Results: {:x => ['1','2','3','4','5','6','7','8'}
76
+ # Why: If receiving data from an HTML form, this makes it easy for a checkbox
77
+ # to pass multiple values from within a single HTML element
78
+ #
79
+ # There are many tests for this library - and you can learn more about the features
80
+ # and usages of deep_merge! by just browsing the test examples
81
+ def DeepMerge.deep_merge!(source, dest, options = {})
82
+ # turn on this line for stdout debugging text
83
+ merge_debug = options[:merge_debug] || false
84
+ overwrite_unmergeable = !options[:preserve_unmergeables]
85
+ knockout_prefix = options[:knockout_prefix] || nil
86
+ if knockout_prefix == "" : raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!"; end
87
+ if knockout_prefix && !overwrite_unmergeable : raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!"; end
88
+ # if present: we will split and join arrays on this char before merging
89
+ array_split_char = options[:unpack_arrays] || false
90
+ # request that we sort together any arrays when they are merged
91
+ sort_merged_arrays = options[:sort_merged_arrays] || false
92
+ di = options[:debug_indent] || ''
93
+ # do nothing if source is nil
94
+ if !source || (source.respond_to?(:blank?) && source.blank?) : return dest; end
95
+ # if dest doesn't exist, then simply copy source to it
96
+ if !(dest) && overwrite_unmergeable : dest = source; return dest; end
97
+
98
+ puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug
99
+ if source.kind_of?(Hash)
100
+ puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug
101
+ source.each do |src_key, src_value|
102
+ if dest.kind_of?(Hash)
103
+ puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
104
+ if dest[src_key]
105
+ puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
106
+ dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + ' '))
107
+ else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!)
108
+ puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
109
+ # note: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others)
110
+ begin
111
+ src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty)
112
+ rescue TypeError
113
+ src_dup = src_value
114
+ end
115
+ dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + ' '))
116
+ end
117
+ else # dest isn't a hash, so we overwrite it completely (if permitted)
118
+ if overwrite_unmergeable
119
+ puts "#{di} overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over-> #{dest.inspect}" if merge_debug
120
+ dest = overwrite_unmergeables(source, dest, options)
121
+ end
122
+ end
123
+ end
124
+ elsif source.kind_of?(Array)
125
+ puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
126
+ # if we are instructed, join/split any source arrays before processing
127
+ if array_split_char
128
+ puts "#{di} split/join on source: #{source.inspect}" if merge_debug
129
+ source = source.join(array_split_char).split(array_split_char)
130
+ if dest.kind_of?(Array) : dest = dest.join(array_split_char).split(array_split_char); end
131
+ end
132
+ # if there's a naked knockout_prefix in source, that means we are to truncate dest
133
+ if source.index(knockout_prefix) : dest = clear_or_nil(dest); source.delete(knockout_prefix); end
134
+ if dest.kind_of?(Array)
135
+ if knockout_prefix
136
+ print "#{di} knocking out: " if merge_debug
137
+ # remove knockout prefix items from both source and dest
138
+ source.delete_if do |ko_item|
139
+ retval = false
140
+ item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}}, "") : ko_item
141
+ if item != ko_item
142
+ print "#{ko_item} - " if merge_debug
143
+ dest.delete(item)
144
+ dest.delete(ko_item)
145
+ retval = true
146
+ end
147
+ retval
148
+ end
149
+ puts if merge_debug
150
+ end
151
+ puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
152
+ dest = dest | source
153
+ if sort_merged_arrays : dest.sort!; end
154
+ elsif overwrite_unmergeable
155
+ puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug
156
+ dest = overwrite_unmergeables(source, dest, options)
157
+ end
158
+ else # src_hash is not an array or hash, so we'll have to overwrite dest
159
+ puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug
160
+ dest = overwrite_unmergeables(source, dest, options)
161
+ end
162
+ puts "#{di}Returning #{dest.inspect}" if merge_debug
163
+ dest
164
+ end # deep_merge!
165
+
166
+ # allows deep_merge! to uniformly handle overwriting of unmergeable entities
167
+ def DeepMerge::overwrite_unmergeables(source, dest, options)
168
+ merge_debug = options[:merge_debug] || false
169
+ overwrite_unmergeable = !options[:preserve_unmergeables]
170
+ knockout_prefix = options[:knockout_prefix] || false
171
+ di = options[:debug_indent] || ''
172
+ if knockout_prefix && overwrite_unmergeable
173
+ if source.kind_of?(String) # remove knockout string from source before overwriting dest
174
+ src_tmp = source.gsub(%r{^#{knockout_prefix}},"")
175
+ elsif source.kind_of?(Array) # remove all knockout elements before overwriting dest
176
+ src_tmp = source.delete_if {|ko_item| ko_item.kind_of?(String) && ko_item.match(%r{^#{knockout_prefix}}) }
177
+ else
178
+ src_tmp = source
179
+ end
180
+ if src_tmp == source # if we didn't find a knockout_prefix then we just overwrite dest
181
+ puts "#{di}#{src_tmp.inspect} -over-> #{dest.inspect}" if merge_debug
182
+ dest = src_tmp
183
+ else # if we do find a knockout_prefix, then we just delete dest
184
+ puts "#{di}\"\" -over-> #{dest.inspect}" if merge_debug
185
+ dest = ""
186
+ end
187
+ elsif overwrite_unmergeable
188
+ dest = source
189
+ end
190
+ dest
191
+ end
192
+
193
+ def DeepMerge::clear_or_nil(obj)
194
+ if obj.respond_to?(:clear)
195
+ obj.clear
196
+ else
197
+ obj = nil
198
+ end
199
+ obj
200
+ end
201
+
202
+ end # module DeepMerge
203
+
204
+ class Hash
205
+ include DeepMerge::DeepMergeHash
206
+ end
@@ -0,0 +1,553 @@
1
+ require 'test/unit'
2
+ require '../lib/deep_merge.rb'
3
+
4
+ class TestDeepMerge < Test::Unit::TestCase
5
+
6
+ def setup
7
+ end
8
+
9
+ # show that Hash object has deep merge capabilities in form of three methods:
10
+ # ko_deep_merge! # uses '--' knockout and overwrites unmergeable
11
+ # deep_merge! # overwrites unmergeable
12
+ # deep_merge # skips unmergeable
13
+ def test_hash_deep_merge
14
+ x = {}
15
+ assert x.respond_to?('deep_merge!'.to_sym)
16
+ hash_src = {'id' => [3,4,5]}
17
+ hash_dest = {'id' => [1,2,3]}
18
+ assert hash_dest.ko_deep_merge!(hash_src)
19
+ assert_equal({'id' => [1,2,3,4,5]}, hash_dest)
20
+
21
+ hash_src = {'id' => [3,4,5]}
22
+ hash_dest = {'id' => [1,2,3]}
23
+ assert hash_dest.deep_merge!(hash_src)
24
+ assert_equal({'id' => [1,2,3,4,5]}, hash_dest)
25
+
26
+ hash_src = {'id' => 'xxx'}
27
+ hash_dest = {'id' => [1,2,3]}
28
+ assert hash_dest.deep_merge(hash_src)
29
+ assert_equal({'id' => [1,2,3]}, hash_dest)
30
+ end
31
+
32
+ FIELD_KNOCKOUT_PREFIX = DeepMerge::DEFAULT_FIELD_KNOCKOUT_PREFIX
33
+
34
+ # tests DeepMerge::deep_merge! function
35
+ def test_deep_merge
36
+ # merge tests (moving from basic to more complex)
37
+
38
+ # test merging an hash w/array into blank hash
39
+ hash_src = {'id' => '2'}
40
+ hash_dst = {}
41
+ DeepMerge::deep_merge!(hash_src.dup, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
42
+ assert_equal hash_src, hash_dst
43
+
44
+ # test merging an hash w/array into blank hash
45
+ hash_src = {'region' => {'id' => ['227', '2']}}
46
+ hash_dst = {}
47
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
48
+ assert_equal hash_src, hash_dst
49
+
50
+ # merge from empty hash
51
+ hash_src = {}
52
+ hash_dst = {"property" => ["2","4"]}
53
+ DeepMerge::deep_merge!(hash_src, hash_dst)
54
+ assert_equal({"property" => ["2","4"]}, hash_dst)
55
+
56
+ # merge to empty hash
57
+ hash_src = {"property" => ["2","4"]}
58
+ hash_dst = {}
59
+ DeepMerge::deep_merge!(hash_src, hash_dst)
60
+ assert_equal({"property" => ["2","4"]}, hash_dst)
61
+
62
+ # simple string overwrite
63
+ hash_src = {"name" => "value"}
64
+ hash_dst = {"name" => "value1"}
65
+ DeepMerge::deep_merge!(hash_src, hash_dst)
66
+ assert_equal({"name" => "value"}, hash_dst)
67
+
68
+ # simple string overwrite of empty hash
69
+ hash_src = {"name" => "value"}
70
+ hash_dst = {}
71
+ DeepMerge::deep_merge!(hash_src, hash_dst)
72
+ assert_equal(hash_src, hash_dst)
73
+
74
+ # hashes holding array
75
+ hash_src = {"property" => ["1","3"]}
76
+ hash_dst = {"property" => ["2","4"]}
77
+ DeepMerge::deep_merge!(hash_src, hash_dst)
78
+ assert_equal(["2","4","1","3"], hash_dst['property'])
79
+
80
+ # hashes holding array (sorted)
81
+ hash_src = {"property" => ["1","3"]}
82
+ hash_dst = {"property" => ["2","4"]}
83
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:sort_merged_arrays => true})
84
+ assert_equal(["1","2","3","4"].sort, hash_dst['property'])
85
+
86
+ # hashes holding hashes holding arrays (array with duplicate elements is merged with dest then src
87
+ hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}}
88
+ hash_dst = {"property" => {"bedroom_count" => ["3", "2"], "bathroom_count" => ["2"]}}
89
+ DeepMerge::deep_merge!(hash_src, hash_dst)
90
+ assert_equal({"property" => {"bedroom_count" => ["3","2","1"], "bathroom_count" => ["2", "1", "4+"]}}, hash_dst)
91
+
92
+ # hash holding hash holding array v string (string is overwritten by array)
93
+ hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}}
94
+ hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}}
95
+ DeepMerge::deep_merge!(hash_src, hash_dst)
96
+ assert_equal({"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}, hash_dst)
97
+
98
+ # hash holding hash holding array v string (string is NOT overwritten by array)
99
+ hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}}
100
+ hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}}
101
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true})
102
+ assert_equal({"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}}, hash_dst)
103
+
104
+ # hash holding hash holding string v array (array is overwritten by string)
105
+ hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}}
106
+ hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
107
+ DeepMerge::deep_merge!(hash_src, hash_dst)
108
+ assert_equal({"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}}, hash_dst)
109
+
110
+ # hash holding hash holding string v array (array does NOT overwrite string)
111
+ hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}}
112
+ hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
113
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true})
114
+ assert_equal({"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}, hash_dst)
115
+
116
+ # hash holding hash holding hash v array (array is overwritten by hash)
117
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}}
118
+ hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
119
+ DeepMerge::deep_merge!(hash_src, hash_dst)
120
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}, hash_dst)
121
+
122
+ # hash holding hash holding hash v array (array is NOT overwritten by hash)
123
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}}
124
+ hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
125
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true})
126
+ assert_equal({"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}, hash_dst)
127
+
128
+ # 3 hash layers holding integers (integers are overwritten by source)
129
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}}
130
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => 2, "queen_bed" => 4}, "bathroom_count" => ["2"]}}
131
+ DeepMerge::deep_merge!(hash_src, hash_dst)
132
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}, hash_dst)
133
+
134
+ # 3 hash layers holding arrays of int (arrays are merged)
135
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => ["1", "4+"]}}
136
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
137
+ DeepMerge::deep_merge!(hash_src, hash_dst)
138
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2","1","4+"]}}, hash_dst)
139
+
140
+ # 1 hash overwriting 3 hash layers holding arrays of int
141
+ hash_src = {"property" => "1"}
142
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
143
+ DeepMerge::deep_merge!(hash_src, hash_dst)
144
+ assert_equal({"property" => "1"}, hash_dst)
145
+
146
+ # 1 hash NOT overwriting 3 hash layers holding arrays of int
147
+ hash_src = {"property" => "1"}
148
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
149
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true})
150
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}, hash_dst)
151
+
152
+ # 3 hash layers holding arrays of int (arrays are merged) but second hash's array is overwritten
153
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}}
154
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
155
+ DeepMerge::deep_merge!(hash_src, hash_dst)
156
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => "1"}}, hash_dst)
157
+
158
+ # 3 hash layers holding arrays of int (arrays are merged) but second hash's array is NOT overwritten
159
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}}
160
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
161
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true})
162
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2"]}}, hash_dst)
163
+
164
+ # 3 hash layers holding arrays of int, but one holds int. This one overwrites, but the rest merge
165
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [1]}, "bathroom_count" => ["1"]}}
166
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
167
+ DeepMerge::deep_merge!(hash_src, hash_dst)
168
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [4,1]}, "bathroom_count" => ["2","1"]}}, hash_dst)
169
+
170
+ # 3 hash layers holding arrays of int, but source is incomplete.
171
+ hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3]}, "bathroom_count" => ["1"]}}
172
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
173
+ DeepMerge::deep_merge!(hash_src, hash_dst)
174
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}, hash_dst)
175
+
176
+ # 3 hash layers holding arrays of int, but source is shorter and has new 2nd level ints.
177
+ hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
178
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
179
+ DeepMerge::deep_merge!(hash_src, hash_dst)
180
+ assert_equal({"property" => {"bedroom_count" => {2=>3, "king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}, hash_dst)
181
+
182
+ # 3 hash layers holding arrays of int, but source is empty
183
+ hash_src = {}
184
+ hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
185
+ DeepMerge::deep_merge!(hash_src, hash_dst)
186
+ assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}, hash_dst)
187
+
188
+ # 3 hash layers holding arrays of int, but dest is empty
189
+ hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
190
+ hash_dst = {}
191
+ DeepMerge::deep_merge!(hash_src, hash_dst)
192
+ assert_equal({"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}, hash_dst)
193
+
194
+ # test parameter management for knockout_prefix and overwrite unmergable
195
+ assert_raise(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => ""})}
196
+ assert_raise(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => ""})}
197
+ assert_raise(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => "--"})}
198
+ assert_nothing_raised(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--"})}
199
+ assert_nothing_raised(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst)}
200
+ assert_nothing_raised(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true})}
201
+
202
+ # hash holding arrays of arrays
203
+ hash_src = {["1", "2", "3"] => ["1", "2"]}
204
+ hash_dst = {["4", "5"] => ["3"]}
205
+ DeepMerge::deep_merge!(hash_src, hash_dst)
206
+ assert_equal({["1","2","3"] => ["1", "2"], ["4", "5"] => ["3"]}, hash_dst)
207
+
208
+ # test merging of hash with blank hash, and make sure that source array split still functions
209
+ hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}}
210
+ hash_dst = {}
211
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
212
+ assert_equal({'property' => {'bedroom_count' => ["1","2","3"]}}, hash_dst)
213
+
214
+ # test merging of hash with blank hash, and make sure that source array split does not function when turned off
215
+ hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}}
216
+ hash_dst = {}
217
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
218
+ assert_equal({'property' => {'bedroom_count' => ["1","2,3"]}}, hash_dst)
219
+
220
+ # test merging into a blank hash with overwrite_unmergeables turned on
221
+ hash_src = {"action"=>"browse", "controller"=>"results"}
222
+ hash_dst = {}
223
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
224
+ assert_equal hash_src, hash_dst
225
+
226
+ # KNOCKOUT_PREFIX testing
227
+ # the next few tests are looking for correct behavior from specific real-world params/session merges
228
+ # using the custom modifiers built for param/session merges
229
+
230
+ [nil, ","].each do |ko_split|
231
+ # typical params/session style hash with knockout_merge elements
232
+ hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}}
233
+ hash_session = {"property"=>{"bedroom_count"=>["1", "2", "3"]}}
234
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split})
235
+ assert_equal({"property"=>{"bedroom_count"=>["2", "3"]}}, hash_session)
236
+
237
+ # typical params/session style hash with knockout_merge elements
238
+ hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}}
239
+ hash_session = {"property"=>{"bedroom_count"=>["3"]}}
240
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split})
241
+ assert_equal({"property"=>{"bedroom_count"=>["3","2"]}}, hash_session)
242
+
243
+ # typical params/session style hash with knockout_merge elements
244
+ hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}}
245
+ hash_session = {"property"=>{"bedroom_count"=>["4"]}}
246
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split})
247
+ assert_equal({"property"=>{"bedroom_count"=>["4","2","3"]}}, hash_session)
248
+
249
+ # typical params/session style hash with knockout_merge elements
250
+ hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}}
251
+ hash_session = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "4"]}}
252
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split})
253
+ assert_equal({"property"=>{"bedroom_count"=>["4","2","3"]}}, hash_session)
254
+
255
+ # typical params/session style hash with knockout_merge elements
256
+ hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1", FIELD_KNOCKOUT_PREFIX+"2", "3", "4"]}}
257
+ hash_session = {"amenity"=>{"id"=>["1", "2"]}}
258
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split})
259
+ assert_equal({"amenity"=>{"id"=>["3","4"]}}, hash_session)
260
+ end
261
+
262
+ # special params/session style hash with knockout_merge elements in form src: ["1","2"] dest:["--1,--2", "3,4"]
263
+ hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,"+FIELD_KNOCKOUT_PREFIX+"2", "3,4"]}}
264
+ hash_session = {"amenity"=>{"id"=>["1", "2"]}}
265
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
266
+ assert_equal({"amenity"=>{"id"=>["3","4"]}}, hash_session)
267
+
268
+ # same as previous but without ko_split value, this merge should fail
269
+ hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,"+FIELD_KNOCKOUT_PREFIX+"2", "3,4"]}}
270
+ hash_session = {"amenity"=>{"id"=>["1", "2"]}}
271
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
272
+ assert_equal({"amenity"=>{"id"=>["1","2","3,4"]}}, hash_session)
273
+
274
+ # special params/session style hash with knockout_merge elements in form src: ["1","2"] dest:["--1,--2", "3,4"]
275
+ hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,2", "3,4", "--5", "6"]}}
276
+ hash_session = {"amenity"=>{"id"=>["1", "2"]}}
277
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
278
+ assert_equal({"amenity"=>{"id"=>["2","3","4","6"]}}, hash_session)
279
+
280
+ # special params/session style hash with knockout_merge elements in form src: ["--1,--2", "3,4", "--5", "6"] dest:["1,2", "3,4"]
281
+ hash_params = {"amenity"=>{"id"=>["#{FIELD_KNOCKOUT_PREFIX}1,#{FIELD_KNOCKOUT_PREFIX}2", "3,4", "#{FIELD_KNOCKOUT_PREFIX}5", "6"]}}
282
+ hash_session = {"amenity"=>{"id"=>["1", "2", "3", "4"]}}
283
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
284
+ assert_equal({"amenity"=>{"id"=>["3","4","6"]}}, hash_session)
285
+
286
+
287
+ hash_src = {"url_regions"=>[], "region"=>{"ids"=>["227,233"]}, "action"=>"browse", "task"=>"browse", "controller"=>"results"}
288
+ hash_dst = {"region"=>{"ids"=>["227"]}}
289
+ DeepMerge::deep_merge!(hash_src.dup, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
290
+ assert_equal({"url_regions"=>[], "region"=>{"ids"=>["227","233"]}, "action"=>"browse", "task"=>"browse", "controller"=>"results"}, hash_dst)
291
+
292
+ hash_src = {"region"=>{"ids"=>["--","227"], "id"=>"230"}}
293
+ hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}}
294
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
295
+ assert_equal({"region"=>{"ids"=>["227"], "id"=>"230"}}, hash_dst)
296
+
297
+ hash_src = {"region"=>{"ids"=>["--","227", "232", "233"], "id"=>"232"}}
298
+ hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}}
299
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
300
+ assert_equal({"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}}, hash_dst)
301
+
302
+ hash_src = {"region"=>{"ids"=>["--,227,232,233"], "id"=>"232"}}
303
+ hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}}
304
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
305
+ assert_equal({"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}}, hash_dst)
306
+
307
+ hash_src = {"region"=>{"ids"=>["--,227,232","233"], "id"=>"232"}}
308
+ hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}}
309
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
310
+ assert_equal({"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}}, hash_dst)
311
+
312
+ hash_src = {"region"=>{"ids"=>["--,227"], "id"=>"230"}}
313
+ hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}}
314
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
315
+ assert_equal({"region"=>{"ids"=>["227"], "id"=>"230"}}, hash_dst)
316
+
317
+ hash_src = {"region"=>{"ids"=>["--,227"], "id"=>"230"}}
318
+ hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"}
319
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
320
+ assert_equal({"region"=>{"ids"=>["227"], "id"=>"230"}, "action"=>"browse", "task"=>"browse",
321
+ "controller"=>"results", "property_order_by"=>"property_type.descr"}, hash_dst)
322
+
323
+ hash_src = {"query_uuid"=>"6386333d-389b-ab5c-8943-6f3a2aa914d7", "region"=>{"ids"=>["--,227"], "id"=>"230"}}
324
+ hash_dst = {"query_uuid"=>"6386333d-389b-ab5c-8943-6f3a2aa914d7", "url_regions"=>[], "region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"}
325
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
326
+ assert_equal({"query_uuid" => "6386333d-389b-ab5c-8943-6f3a2aa914d7", "url_regions"=>[],
327
+ "region"=>{"ids"=>["227"], "id"=>"230"}, "action"=>"browse", "task"=>"browse",
328
+ "controller"=>"results", "property_order_by"=>"property_type.descr"}, hash_dst)
329
+
330
+ # knock out entire dest hash if "--" is passed for source
331
+ hash_params = {'amenity' => "--"}
332
+ hash_session = {"amenity" => "1"}
333
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
334
+ assert_equal({'amenity' => ""}, hash_session)
335
+
336
+ # knock out entire dest hash if "--" is passed for source
337
+ hash_params = {'amenity' => ["--"]}
338
+ hash_session = {"amenity" => "1"}
339
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
340
+ assert_equal({'amenity' => []}, hash_session)
341
+
342
+ # knock out entire dest hash if "--" is passed for source
343
+ hash_params = {'amenity' => "--"}
344
+ hash_session = {"amenity" => ["1"]}
345
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
346
+ assert_equal({'amenity' => ""}, hash_session)
347
+
348
+ # knock out entire dest hash if "--" is passed for source
349
+ hash_params = {'amenity' => ["--"]}
350
+ hash_session = {"amenity" => ["1"]}
351
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
352
+ assert_equal({'amenity' => []}, hash_session)
353
+
354
+ # knock out entire dest hash if "--" is passed for source
355
+ hash_params = {'amenity' => ["--"]}
356
+ hash_session = {"amenity" => "1"}
357
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
358
+ assert_equal({'amenity' => []}, hash_session)
359
+
360
+ # knock out entire dest hash if "--" is passed for source
361
+ hash_params = {'amenity' => ["--", "2"]}
362
+ hash_session = {'amenity' => ["1", "3", "7+"]}
363
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
364
+ assert_equal({'amenity' => ["2"]}, hash_session)
365
+
366
+ # knock out entire dest hash if "--" is passed for source
367
+ hash_params = {'amenity' => ["--", "2"]}
368
+ hash_session = {'amenity' => "5"}
369
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
370
+ assert_equal({'amenity' => ['2']}, hash_session)
371
+
372
+ # knock out entire dest hash if "--" is passed for source
373
+ hash_params = {'amenity' => "--"}
374
+ hash_session = {"amenity"=>{"id"=>["1", "2", "3", "4"]}}
375
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
376
+ assert_equal({'amenity' => ""}, hash_session)
377
+
378
+ # knock out entire dest hash if "--" is passed for source
379
+ hash_params = {'amenity' => ["--"]}
380
+ hash_session = {"amenity"=>{"id"=>["1", "2", "3", "4"]}}
381
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","})
382
+ assert_equal({'amenity' => []}, hash_session)
383
+
384
+ # knock out dest array if "--" is passed for source
385
+ hash_params = {"region" => {'ids' => FIELD_KNOCKOUT_PREFIX}}
386
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"]}}
387
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
388
+ assert_equal({'region' => {'ids' => ""}}, hash_session)
389
+
390
+ # knock out dest array but leave other elements of hash intact
391
+ hash_params = {"region" => {'ids' => FIELD_KNOCKOUT_PREFIX}}
392
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}}
393
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
394
+ assert_equal({'region' => {'ids' => "", 'id'=>'11'}}, hash_session)
395
+
396
+ # knock out entire tree of dest hash
397
+ hash_params = {"region" => FIELD_KNOCKOUT_PREFIX}
398
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}}
399
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
400
+ assert_equal({'region' => ""}, hash_session)
401
+
402
+ # knock out entire tree of dest hash - retaining array format
403
+ hash_params = {"region" => {'ids' => [FIELD_KNOCKOUT_PREFIX]}}
404
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}}
405
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
406
+ assert_equal({'region' => {'ids' => [], 'id'=>'11'}}, hash_session)
407
+
408
+ # knock out entire tree of dest hash & replace with new content
409
+ hash_params = {"region" => {'ids' => ["2", FIELD_KNOCKOUT_PREFIX, "6"]}}
410
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}}
411
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
412
+ assert_equal({'region' => {'ids' => ["2", "6"], 'id'=>'11'}}, hash_session)
413
+
414
+ # knock out entire tree of dest hash & replace with new content
415
+ hash_params = {"region" => {'ids' => ["7", FIELD_KNOCKOUT_PREFIX, "6"]}}
416
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}}
417
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
418
+ assert_equal({'region' => {'ids' => ["7", "6"], 'id'=>'11'}}, hash_session)
419
+
420
+ # edge test: make sure that when we turn off knockout_prefix that all values are processed correctly
421
+ hash_params = {"region" => {'ids' => ["7", "--", "2", "6,8"]}}
422
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}}
423
+ DeepMerge::deep_merge!(hash_params, hash_session, {:unpack_arrays => ","})
424
+ assert_equal({'region' => {'ids' => ["1", "2", "3", "4", "7", "--", "6", "8"], 'id'=>'11'}}, hash_session)
425
+
426
+ # edge test 2: make sure that when we turn off source array split that all values are processed correctly
427
+ hash_params = {"region" => {'ids' => ["7", "3", "--", "6,8"]}}
428
+ hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}}
429
+ DeepMerge::deep_merge!(hash_params, hash_session)
430
+ assert_equal({'region' => {'ids' => ["1", "2", "3", "4", "7", "--", "6,8"], 'id'=>'11'}}, hash_session)
431
+
432
+ # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""}
433
+ hash_params = {"amenity"=>"--1"}
434
+ hash_session = {"amenity"=>"1"}
435
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
436
+ assert_equal({"amenity"=>""}, hash_session)
437
+
438
+ # Example: src = {'key' => "--1"}, dst = {'key' => "2"} -> merges to {'key' => ""}
439
+ hash_params = {"amenity"=>"--1"}
440
+ hash_session = {"amenity"=>"2"}
441
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
442
+ assert_equal({"amenity"=>""}, hash_session)
443
+
444
+ # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""}
445
+ hash_params = {"amenity"=>["--1"]}
446
+ hash_session = {"amenity"=>"1"}
447
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
448
+ assert_equal({"amenity"=>[]}, hash_session)
449
+
450
+ # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""}
451
+ hash_params = {"amenity"=>["--1"]}
452
+ hash_session = {"amenity"=>["1"]}
453
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
454
+ assert_equal({"amenity"=>[]}, hash_session)
455
+
456
+ # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""}
457
+ hash_params = {"amenity"=>"--1"}
458
+ hash_session = {}
459
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
460
+ assert_equal({"amenity"=>""}, hash_session)
461
+
462
+
463
+ # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""}
464
+ hash_params = {"amenity"=>"--1"}
465
+ hash_session = {"amenity"=>["1"]}
466
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
467
+ assert_equal({"amenity"=>""}, hash_session)
468
+
469
+ #are unmerged hashes passed unmodified w/out :unpack_arrays?
470
+ hash_params = {"amenity"=>{"id"=>["26,27"]}}
471
+ hash_session = {}
472
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX})
473
+ assert_equal({"amenity"=>{"id"=>["26,27"]}}, hash_session)
474
+
475
+ #hash should be merged
476
+ hash_params = {"amenity"=>{"id"=>["26,27"]}}
477
+ hash_session = {}
478
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
479
+ assert_equal({"amenity"=>{"id"=>["26","27"]}}, hash_session)
480
+
481
+ # second merge of same values should result in no change in output
482
+ hash_params = {"amenity"=>{"id"=>["26,27"]}}
483
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
484
+ assert_equal({"amenity"=>{"id"=>["26","27"]}}, hash_session)
485
+
486
+ #hashes with knockout values are suppressed
487
+ hash_params = {"amenity"=>{"id"=>["#{FIELD_KNOCKOUT_PREFIX}26,#{FIELD_KNOCKOUT_PREFIX}27,28"]}}
488
+ hash_session = {}
489
+ DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","})
490
+ assert_equal({"amenity"=>{"id"=>["28"]}}, hash_session)
491
+
492
+ hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'}
493
+ hash_dst= {'region' =>{'ids'=>['227','2','3','3']}, 'query_uuid' => 'zzz'}
494
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
495
+ assert_equal({'region' =>{'ids'=>[]}, 'query_uuid' => 'zzz'}, hash_dst)
496
+
497
+ hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'}
498
+ hash_dst= {'region' =>{'ids'=>['227','2','3','3'], 'id' => '3'}, 'query_uuid' => 'zzz'}
499
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
500
+ assert_equal({'region' =>{'ids'=>[], 'id'=>'3'}, 'query_uuid' => 'zzz'}, hash_dst)
501
+
502
+ hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'}
503
+ hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'}
504
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
505
+ assert_equal({'region' =>{'muni_city_id' => '2244', 'ids'=>[], 'id'=>'3'}, 'query_uuid' => 'zzz'}, hash_dst)
506
+
507
+ hash_src= {'region' =>{'ids'=>['--'], 'id' => '5'}, 'query_uuid' => 'zzz'}
508
+ hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'}
509
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
510
+ assert_equal({'region' =>{'muni_city_id' => '2244', 'ids'=>[], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst)
511
+
512
+ hash_src= {'region' =>{'ids'=>['--', '227'], 'id' => '5'}, 'query_uuid' => 'zzz'}
513
+ hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'}
514
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
515
+ assert_equal({'region' =>{'muni_city_id' => '2244', 'ids'=>['227'], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst)
516
+
517
+ hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>'--', 'id'=>'5'}, 'query_uuid' => 'zzz'}
518
+ hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'}
519
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
520
+ assert_equal({'region' =>{'muni_city_id' => '', 'ids'=>'', 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst)
521
+
522
+ hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>['--'], 'id'=>'5'}, 'query_uuid' => 'zzz'}
523
+ hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'}
524
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
525
+ assert_equal({'region' =>{'muni_city_id' => '', 'ids'=>[], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst)
526
+
527
+ hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>['--','227'], 'id'=>'5'}, 'query_uuid' => 'zzz'}
528
+ hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'}
529
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
530
+ assert_equal({'region' =>{'muni_city_id' => '', 'ids'=>['227'], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst)
531
+
532
+ hash_src = {"muni_city_id"=>"--", "id"=>""}
533
+ hash_dst = {"muni_city_id"=>"", "id"=>""}
534
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
535
+ assert_equal({"muni_city_id"=>"", "id"=>""}, hash_dst)
536
+
537
+ hash_src = {"region"=>{"muni_city_id"=>"--", "id"=>""}}
538
+ hash_dst = {"region"=>{"muni_city_id"=>"", "id"=>""}}
539
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
540
+ assert_equal({"region"=>{"muni_city_id"=>"", "id"=>""}}, hash_dst)
541
+
542
+ hash_src = {"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"--", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"}
543
+ hash_dst = {"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"}
544
+ DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","})
545
+ assert_equal({"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"}, hash_dst)
546
+
547
+ # hash of array of hashes
548
+ hash_src = {"item" => [{"1" => "3"}, {"2" => "4"}]}
549
+ hash_dst = {"item" => [{"3" => "5"}]}
550
+ DeepMerge::deep_merge!(hash_src, hash_dst)
551
+ assert_equal({"item" => [{"3" => "5"}, {"1" => "3"}, {"2" => "4"}]}, hash_dst)
552
+ end # test_deep_merge
553
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deep_merge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Steve Midgley
8
+ autorequire: deep_merge
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-18 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: public@misuse.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - lib/deep_merge.rb
26
+ - test/test_deep_merge.rb
27
+ - README
28
+ has_rdoc: true
29
+ homepage:
30
+ post_install_message:
31
+ rdoc_options: []
32
+
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project:
50
+ rubygems_version: 0.9.5
51
+ signing_key:
52
+ specification_version: 2
53
+ summary: Permits recursive/deep merges of arrays and hashes.
54
+ test_files: []
55
+