hashmodel 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@ Given /^we have a test table$/ do |table|
3
3
  end
4
4
 
5
5
  Given /^we have a HashModel instance$/ do
6
- @hm = MikBe::HashModel.new
6
+ @hm = HashModel.new
7
7
  end
8
8
 
9
9
  When /^the HashModel is populated with the test table$/ do
@@ -35,7 +35,7 @@ end
35
35
 
36
36
  Then /^all the siblings should have the same group id$/ do
37
37
  group_ids = []
38
- @siblings.each {|record| group_ids << record[:hm_group_id]}
38
+ @siblings.each {|record| group_ids << record[:_group_id]}
39
39
  group_ids.uniq.length.should == 1
40
40
  group_ids.uniq[0].should_not == nil
41
41
  end
@@ -1,4 +1,4 @@
1
- module MikBe
1
+ class HashModel
2
2
 
3
3
  class ReservedNameError < StandardError ; end
4
4
 
@@ -1,411 +1,503 @@
1
- # Pickle Pumpers namespace
2
- module MikBe
3
-
4
- # A simple MVC type model class for storing hashes as flattenable, searchable records
5
- class HashModel
6
- include Enumerable
7
-
8
- def initialize(parameters={})
9
- # Initialize variables
10
- clear
11
-
12
- # Map Array methods
13
- mimic_methods
14
-
15
- # Set values given as hashes
16
- parameters.each { |key,value| instance_variable_set("@#{key}", value) }
1
+ # A simple MVC type model class for storing hashes as flattenable, searchable records
2
+ class HashModel
3
+ include Enumerable
4
+
5
+ def initialize(parameters={})
6
+ # Initialize variables
7
+ clear
17
8
 
18
- check_field_names(@raw_data) if !@raw_data.empty?
19
-
20
- # Setup the flat data
21
- flatten
22
-
9
+ # Map Array methods
10
+ mimic_methods
11
+
12
+ # Set values given as hashes
13
+ parameters.each { |key,value| instance_variable_set("@#{key}", value) }
14
+
15
+ # Allow a single hash to be added with :raw_data
16
+ @raw_data = [@raw_data] if @raw_data.class == Hash
17
+
18
+ check_field_names(@raw_data) if !@raw_data.empty?
19
+
20
+ # Setup the flat data
21
+ flatten
22
+
23
+ end
24
+
25
+ ## Properties
26
+
27
+ attr_accessor :flatten_index, :raw_data
28
+
29
+ # Sets field name used to flatten the recordset
30
+ def flatten_index=(value)
31
+ @flatten_index = value
32
+ flatten
33
+ end
34
+
35
+ # Are the records being filtered?
36
+ def filtered?
37
+ !!@filter
38
+ end
39
+
40
+ # Trap changes to raw data so we can re-flatten the data
41
+ def raw_data=(value)
42
+ value = [] if value.nil?
43
+ raise SyntaxError, "Raw data may only be an array of hashes" if value.class != Array
44
+ check_field_names(value)
45
+ @raw_data = value.clone
46
+ end
47
+
48
+
49
+ ## Public Methods
50
+
51
+ # Freeze all the data properties
52
+ def freeze
53
+ instance_variables.each do |var|
54
+ instance_eval("#{var}.freeze")
23
55
  end
24
-
25
- ## Properties
56
+ super
57
+ end
58
+
59
+ # Remove the in-place where filter
60
+ def clear_filter
61
+ @filter = nil
62
+ flatten
63
+ end
64
+ alias :clear_where :clear_filter # in case this makes more sense to people
65
+
66
+ # Reset the HashModel
67
+ def clear
68
+ @raw_data = []
69
+ @modified_data = []
70
+ @unflatten_data = []
71
+ @flatten_index = nil
72
+ @filter = nil
73
+ end
26
74
 
27
- attr_accessor :flatten_index, :raw_data
28
75
 
29
- # Sets field name used to flatten the recordset
30
- def flatten_index=(value)
31
- @flatten_index = value
32
- flatten
76
+ ## Operators
77
+
78
+ # Overload Array#<< function so we can create the flatten index as the first record is added
79
+ # and allows us to send back this instance of the HashModel instead of an array.
80
+ def <<(value)
81
+ case value
82
+ when HashModel
83
+ @raw_data.concat(value.raw_data)
84
+ when Hash
85
+ # unflatten if needed
86
+ value = unflatten(value) unless value.to_s.match("__").nil?
87
+ check_field_names(value)
88
+ @raw_data << value
89
+ when Array
90
+ # It goes crazy if you don't clone the array before recursing
91
+ value.clone.each{ |member| self << member }
92
+ else
93
+ raise SyntaxError, "You may only add a hash, another HashModel, or an array of either"
33
94
  end
95
+ flatten
96
+ end
97
+ # I like the method name "add" for adding to recordsets seems more natural
98
+ alias :add :<<
99
+ alias :concat :<<
100
+ alias :push :<<
101
+
102
+ # remap... no loops... you know the deal
103
+ alias :_equals_ :==
34
104
 
35
- # Are the records being filtered?
36
- def filtered?
37
- !!@filter
105
+ # Compare values with the HashModel based on the type of values given
106
+ def ==(value)
107
+ flatten
108
+ case value
109
+ when HashModel
110
+ @raw_data == value.raw_data &&
111
+ @flatten_index == value.flatten_index &&
112
+ @modified_data == value
113
+ when Array
114
+ # test for something other than hashes, a flattened recordset, or raw data
115
+ if !value.empty? && value[0].class == Hash && value[0].has_key?(:_group_id)
116
+ @modified_data == value
117
+ else
118
+ @raw_data == value
119
+ end
120
+ else
121
+ false
38
122
  end
123
+ end
124
+ alias :eql? :==
125
+
39
126
 
40
- # Trap changes to raw data so we can re-flatten the data
41
- def raw_data=(value)
42
- value = [] if value.nil?
43
- raise SyntaxError, "Raw data may only be an array of hashes" if value.class != Array
44
- check_field_names(value)
45
- @raw_data = value.clone
127
+ # Remap spaceship to stop infinite loops
128
+ alias :_spaceship_ :<=>
129
+ private :_spaceship_
130
+
131
+ # Spaceship - Don't probe me bro'!
132
+ def <=>(value)
133
+ case value
134
+ when HashModel
135
+ _spaceship_(value)
136
+ when Array
137
+ # test for a flattened recordset or raw data
138
+ if !value.empty? && value[0].has_key?(:_group_id)
139
+ @modified_data <=> value
140
+ else
141
+ @raw_data <=> value
142
+ end
143
+ else
144
+ nil
46
145
  end
146
+ end
47
147
 
48
148
 
49
- ## Public Methods
149
+ ## Searching
50
150
 
51
- # Freeze the raw data
52
- def freeze
53
- @raw_data.freeze
54
- @modified_data.freeze
55
- @flatten_index.freeze
56
- @filter.freeze
57
- super
58
- end
59
-
60
- # Remove the where filter
61
- def clear_filter
62
- @filter = nil
63
- flatten
64
- end
65
- alias :clear_where :clear_filter # in case this makes more sense to people
151
+ # Tests flat or raw data depending of if you use flat or raw data
152
+ def include?(value)
153
+ return false if value.class != Hash
154
+ @modified_data.include?(value) || @raw_data.include?(value)
155
+ end
156
+
157
+ # Search creating a new instance of HashModel based on this one
158
+ def where(value=nil, &search)
159
+ self.clone.where!(value, &search)
160
+ end
161
+
162
+ # Search the flattened records using a
163
+ def where!(value=nil, &search)
164
+ # Parameter checks
165
+ raise SyntaxError, "You may only provide a parameter or a block but not both" if value && !search.nil?
66
166
 
67
- # Reset the HashModel
68
- def clear
69
- @raw_data = []
70
- @modified_data = []
71
- @flatten_index = nil
167
+ # Allow clearing the filter and returning the entire recordset if nothing is given
168
+ if !value && search.nil?
72
169
  @filter = nil
170
+ return flatten
73
171
  end
74
-
75
172
 
76
- ## Operators
173
+ # If given a parameter make our own search based on the flatten index
174
+ if !value.nil?
175
+ # Make sure the field name is available to the proc
176
+ flatten_index = @flatten_index
177
+ search = proc do
178
+ instance_variable_get("@#{flatten_index}") == value
179
+ end # search
180
+ end # !value.nil?
77
181
 
78
- # Overload Array#<< function so we can create the flatten index as the first record is added
79
- # and allows us to send back this instance of the HashModel instead of an array.
80
- def <<(value)
81
- case value
82
- when HashModel
83
- @raw_data.concat(value.raw_data)
84
- when Hash
85
- check_field_names(value)
86
- @raw_data << value
87
- when Array
88
- # It goes crazy if you don't clone the array before recursing
89
- value.clone.each{ |member| self << member }
90
- else
91
- raise SyntaxError, "You may only add a hash, another HashModel, or an array of either"
92
- end
93
- flatten
182
+ # Set and process the filter
183
+ @filter = search
184
+ flatten
185
+ end
186
+
187
+ # Return the other records created from the same raw data record as the one(s) searched for
188
+ def group(value=nil, &search)
189
+ if !value.nil? || !search.nil?
190
+ sibling = where(value, &search)
191
+ else
192
+ sibling = where &@filter
94
193
  end
95
- # I like the method name "add" for adding to recordsets seems more natural
96
- alias :add :<<
97
- alias :concat :<<
98
- alias :push :<<
99
194
 
100
- # remap... no loops... you know the deal
101
- alias :_equals_ :==
195
+ # Get all the unique group id's
196
+ group_ids = sibling.collect {|hash| hash[:_group_id]}.uniq
102
197
 
103
- # Compare values with the HashModel based on the type of values given
104
- def ==(value)
105
- flatten
106
- case value
107
- when HashModel
108
- @raw_data == value.raw_data &&
109
- @flatten_index == value.flatten_index &&
110
- @modified_data == value
111
- when Array
112
- # test for something other than hashes, a flattened recordset, or raw data
113
- if !value.empty? && value[0].class == Hash && value[0].has_key?(:hm_group_id)
114
- @modified_data == value
115
- else
116
- @raw_data == value
117
- end
118
- else
119
- false
120
- end
121
- end
122
- alias :eql? :==
123
-
124
-
125
- # Remap spaceship to stop infinite loops
126
- alias :_spaceship_ :<=>
127
- private :_spaceship_
198
+ # Find any records with matching group ids
199
+ where {group_ids.include? @_group_id}
200
+ end
201
+
202
+ # Group the records in place based on the existing filter
203
+ # This is basically a short hand for filtering based on
204
+ # group ids of filtered records
205
+ def group!(value=nil, &search)
128
206
 
129
- # Spaceship - Don't probe me bro'!
130
- def <=>(value)
131
- case value
132
- when HashModel
133
- _spaceship_(value)
134
- when Array
135
- # test for a flattened recordset or raw data
136
- if !value.empty? && value[0].has_key?(:hm_group_id)
137
- @modified_data <=> value
138
- else
139
- @raw_data <=> value
140
- end
141
- else
142
- nil
143
- end
207
+ if !value.nil? || !search.nil?
208
+ where!(value, &search)
144
209
  end
210
+
211
+ # Get all the unique group id's
212
+ group_ids = @modified_data.collect {|hash| hash[:_group_id]}.uniq
145
213
 
214
+ # Find any records with matching group ids
215
+ where! {group_ids.include? @_group_id}
216
+ end
146
217
 
147
- ## Searching
148
-
149
- # Tests flat or raw data depending of if you use flat or raw data
150
- def include?(value)
151
- return false if value.class != Hash
152
- @modified_data.include?(value) || @raw_data.include?(value)
153
- end
218
+ # Find the raw data record for a given flat record
219
+ def parent(flat_record)
220
+ flatten
221
+ @raw_data[flat_record[:_group_id]]
222
+ end
154
223
 
155
- # Search creating a new instance of HashModel based on this one
156
- def where(value=nil, &search)
157
- self.clone.where!(value, &search)
158
- end
224
+ # Set the array value for self to the flattened hashes based on the flatten_index
225
+ def flatten
226
+ # Don't flatten the data if we don't need to
227
+ return self if !dirty?
228
+
229
+ id = -1
230
+ group_id = -1
231
+ @modified_data.clear
232
+ # set the flatten index if this is the first time the function is called
233
+ @flatten_index = @raw_data[0].keys[0] if @raw_data != [] && @flatten_index.nil?
234
+ flatten_index = @flatten_index.to_s
159
235
 
160
- # Search the flattened records using a
161
- def where!(value=nil, &search)
162
- # Parameter checks
163
- raise SyntaxError, "You may only provide a parameter or a block but not both" if value && !search.nil?
236
+ # Flatten and filter the raw data
237
+ @raw_data.each do |record|
238
+ new_records, duplicate_data = flatten_hash(record, flatten_index)
239
+ # catch raw data records that don't have the flatten index
240
+ new_records << {@flatten_index.to_sym=>nil} if new_records.empty?
241
+ group_id += 1
242
+ new_records.collect! do |new_record|
243
+ # Double bangs aren't needed but are they more efficient?
244
+ new_record.merge!( duplicate_data.merge!( { :_id=>(id+=1), :_group_id=>group_id } ) )
245
+ end
164
246
 
165
- # Allow clearing the filter and returning the entire recordset if nothing is given
166
- if !value && search.nil?
167
- @filter = nil
168
- return flatten
247
+ # Add the records to modified data if they pass the filter
248
+ new_records.each do |new_record|
249
+ @modified_data << new_record if @filter.nil? ? true : (create_object_from_flat_hash(new_record).instance_eval &@filter)
169
250
  end
251
+ end # raw_data.each
252
+ set_dirty_hash
253
+ self
254
+ end # flatten
255
+
256
+ # If the hash_model has been changed but not flattened
257
+ def dirty?
258
+ get_current_dirty_hash != @dirty_hash
259
+ end
170
260
 
171
- # If given a parameter make our own search based on the flatten index
172
- if !value.nil?
173
- # Make sure the field name is available to the proc
174
- flatten_index = @flatten_index
175
- search = proc do
176
- instance_variable_get("@#{flatten_index}") == value
177
- end # search
178
- end # !value.nil?
179
-
180
- # Set and process the filter
181
- @filter = search
182
- flatten
261
+ # Return a string consisting of the flattened data
262
+ def to_s
263
+ @modified_data.to_s
264
+ end
265
+
266
+ # Return an array of the flattened data
267
+ def to_ary
268
+ @modified_data.to_ary
269
+ end
270
+
271
+ # Iterate over the flattened records
272
+ def each
273
+ @modified_data.each do |record|
274
+ # change or manipulate the values in your value array inside this block
275
+ yield record
183
276
  end
277
+ end
278
+
279
+ # Convert a flat record into an unflattened record
280
+ def unflatten(input)
281
+ HashModel.unflatten(input)
282
+ end
184
283
 
185
- # Return the other records created from the same raw data record as the one(s) searched for
186
- def group(value=nil, &search)
187
- if !value.nil? || !search.nil?
188
- sibling = where(value, &search)
189
- else
190
- sibling = where &@filter
191
- end
192
-
193
- # Get all the unique group id's
194
- group_ids = sibling.collect {|hash| hash[:hm_group_id]}.uniq
284
+ # Convert a flat record into an unflattened record
285
+ def self.unflatten(input)
286
+ # Seriously in need of a refactor, just looking at this hurts my brain
287
+ # There's a lot of redundancy here.
288
+ case input
289
+ when Hash
290
+ new_record = {}
291
+ input.each do |key, value|
292
+ # recursively look for flattened keys
293
+ keys = key.to_s.split("__", 2)
294
+ if keys[1]
295
+ key = keys[0].to_sym
296
+ value = unflatten({keys[1].to_sym => value})
297
+ end
195
298
 
196
- # Find any records with matching group ids
197
- where {group_ids.include? @hm_group_id}
299
+ # Don't overwrite existing value
300
+ if (existing = new_record[key])
301
+ # convert to array and search for subkeys if appropriate
302
+ if existing.class == Hash
303
+ # Convert to an array if something other than a hash is added
304
+ unless value.class == Hash
305
+ new_record[key] = hash_to_array(existing)
306
+ new_record[key] << value
307
+ else
308
+ # Search subkeys for duplicate values if it's a hash
309
+ unless (found_keys = existing.keys & value.keys).empty?
310
+ found_keys.each do |found_key|
311
+ # How can I remove this redundancy?
312
+ if new_record[key][found_key].class == Hash
313
+ unless value[found_key].class == Hash
314
+ new_record[key] = hash_to_array(new_record[key][found_key])
315
+ new_record[key] << value[found_key]
316
+ else
317
+ new_record[key][found_key].merge!(value[found_key])
318
+ end
319
+ end
320
+ end
321
+ else
322
+ new_record[key].merge!(value)
323
+ end
324
+ end
325
+ else
326
+ new_record[key] << value
327
+ end
328
+ else
329
+ new_record.merge!(key => value)
330
+ end
331
+ end
332
+ new_record
333
+ when Array
334
+ # recurse into array
335
+ input.collect! {|item| unflatten(item) }
336
+ else
337
+ input
198
338
  end
199
-
200
- # Group the records in place based on the existing filter
201
- # This is basically a short hand for filtering based on
202
- # group ids of filtered records
203
- def group!(value=nil, &search)
204
-
205
- if !value.nil? || !search.nil?
206
- where!(value, &search)
207
- end
208
-
209
- # Get all the unique group id's
210
- group_ids = @modified_data.collect {|hash| hash[:hm_group_id]}.uniq
339
+ end
340
+
341
+ private
211
342
 
212
- # Find any records with matching group ids
213
- where! {group_ids.include? @hm_group_id}
214
- end
215
343
 
216
- # Find the raw data record for a given flat record
217
- def parent(flat_record)
218
- flatten
219
- @raw_data[flat_record[:hm_group_id]]
344
+ # Convert a hash of multiple key/value pairs to an array of single hashes.
345
+ # {:field1 => "value1", :field2 => "value2"}
346
+ # becomes
347
+ # [{:field1 => "value1"}, {:field2 => "value2"}]
348
+ def self.hash_to_array(hash)
349
+ array = []
350
+ hash.each do |key, value|
351
+ array << {key => value}
220
352
  end
353
+ array
354
+ end
221
355
 
222
- # Set the array value for self to the flattened hashes based on the flatten_index
223
- def flatten
224
- # Don't flatten the data if we don't need to
225
- return self if !dirty?
226
-
227
- id = -1
228
- group_id = -1
229
- @modified_data.clear
230
- # set the flatten index if this is the first time the function is called
231
- @flatten_index = @raw_data[0].keys[0] if @raw_data != [] && @flatten_index.nil?
232
- flatten_index = @flatten_index.to_s
233
-
234
- # Flatten and filter the raw data
235
- @raw_data.each do |record|
236
- new_records, duplicate_data = flatten_hash(record, flatten_index)
237
- # catch raw data records that don't have the flatten index
238
- new_records << {@flatten_index.to_sym=>nil} if new_records.empty?
239
- group_id += 1
240
- new_records.collect! do |new_record|
241
- # Double bangs aren't needed but are they more efficient?
242
- new_record.merge!( duplicate_data.merge!( { :hm_id=>(id+=1), :hm_group_id=>group_id } ) )
243
- end
244
-
245
- # Add the records to modified data if they pass the filter
246
- new_records.each do |new_record|
247
- @modified_data << new_record if @filter.nil? ? true : (create_object_from_flat_hash(new_record).instance_eval &@filter)
248
- end
249
- end # raw_data.each
250
- set_dirty_hash
251
- self
252
- end # flatten
253
-
254
- # If the hash_model has been changed but not flattened
255
- def dirty?
256
- get_current_dirty_hash != @dirty_hash
257
- end
356
+ # Convert a hash of multiple key/value pairs to an array of single hashes.
357
+ # {:field1 => "value1", :field2 => "value2"}
358
+ # becomes
359
+ # [{:field1 => "value1"}, {:field2 => "value2"}]
360
+ def hash_to_array(hash)
361
+ HashModel.hash_to_array(hash)
362
+ end
258
363
 
259
- # Return a string consisting of the flattened data
260
- def to_s
261
- @modified_data.to_s
364
+ # Checks hash keys for reserved field names
365
+ def check_field_names(input)
366
+ case input
367
+ when Hash
368
+ input.each do |key, value|
369
+ raise ReservedNameError, "use of reserved name :#{key} as a field name." if [:_id, :_group_id].include?(key)
370
+ check_field_names(value)
371
+ end
372
+ when Array
373
+ input.clone.each { |record| check_field_names(record) }
262
374
  end
375
+ end
263
376
 
264
- # Return an array of the flattened data
265
- def to_ary
266
- @modified_data.to_ary
267
- end
268
-
269
- # Iterate over the flattened records
270
- def each
271
- @modified_data.each do |record|
272
- # change or manipulate the values in your value array inside this block
273
- yield record
274
- end
275
- end
276
-
277
- private
278
-
279
- # Checks hash keys for reserved field names
280
- def check_field_names(input)
281
- case input
282
- when Hash
283
- input.each do |key, value|
284
- raise ReservedNameError, "use of reserved name :#{key} as a field name." if [:hm_id, :hm_group_id].include?(key)
285
- check_field_names(value)
286
- end
287
- when Array
288
- input.clone.each { |record| check_field_names(record) }
289
- end
290
- end
377
+ # Save a hash for later evaluation
378
+ def set_dirty_hash
379
+ @dirty_hash = get_current_dirty_hash
380
+ end
381
+
382
+ # Create a hash based on internal values
383
+ def get_current_dirty_hash
384
+ # self.hash won't work
385
+ [@raw_data.hash, @filter.hash, @flatten_index.hash].hash
386
+ end
387
+
388
+ # Recursively convert a single record into an array of new
389
+ # records that are flattened based on the given flattened hash key
390
+ # e.g. {:x=>{:x1=>1}, :y=>{:y1=>{:y2=>2,:y3=>4}, y4:=>5}, :z=>6}
391
+ # if you wanted to flatten to :x1 you would set flatten_index to :x_x1
392
+ # To flatten to :y2 you would set flatten_index to :y_y1_y2
393
+ def flatten_hash(input, flatten_index, recordset=[], duplicate_data={}, parent_key=nil)
394
+ case input
395
+ when Hash
396
+ # Check to see if the found key is on this level - We need to add duplicate data differently if so
397
+ found_key = (input.select { |key, value| flatten_index == "#{parent_key}#{"__" if !parent_key.nil?}#{key}"} != {})
398
+
399
+ # Add records for matching flatten fields and save duplicate record data for later addition to each record.
400
+ input.each do |key, value|
401
+ #puts "\nkey: #{key}; value: #{value}"
402
+ flat_key = "#{parent_key}#{"__" if !parent_key.nil?}#{key}"
291
403
 
292
- # Save a hash for later evaluation
293
- def set_dirty_hash
294
- @dirty_hash = get_current_dirty_hash
295
- end
296
-
297
- # Create a hash based on internal values
298
- def get_current_dirty_hash
299
- # self.hash won't work
300
- [@raw_data.hash, @filter.hash, @flatten_index.hash].hash
301
- end
302
-
303
- # Recursively convert a single record into an array of new
304
- # records that are flattened based on the given flattened hash key
305
- # e.g. {:x=>{:x1=>1}, :y=>{:y1=>{:y2=>2,:y3=>4}, y4:=>5}, :z=>6}
306
- # if you wanted to flatten to :x1 you would set flatten_index to :x_x1
307
- # To flatten to :y2 you would set flatten_index to :y_y1_y2
308
- def flatten_hash(input, flatten_index, recordset=[], duplicate_data={}, parent_key=nil)
309
- case input
310
- when Hash
311
- # Check to see if the found key is on this level - We need to add duplicate data differently if so
312
- found_key = (input.select { |key, value| flatten_index == "#{parent_key}#{"_" if !parent_key.nil?}#{key}"} != {})
404
+ #puts "flat_key: #{flat_key}"
405
+ #puts "flat_index: #{flatten_index}"
406
+
407
+ flat_key_starts_with_flatten_index = flat_key.start_with?("#{flatten_index}__")
408
+ flatten_index_starts_with_flat_key = flatten_index.start_with?(flat_key)
313
409
 
314
- # Add records for matching flatten fields and save duplicate record data for later addition to each record.
315
- input.each do |key, value|
316
- flat_key = "#{parent_key}#{"_" if !parent_key.nil?}#{key}"
317
- flat_key_starts_with_flatten_index = flat_key.start_with?(flatten_index)
318
- flatten_index_starts_with_flat_key = flatten_index.start_with?(flat_key)
319
- # figure out what we need to do based on where we're at in the record's value tree and man does it look ugly
320
- if flat_key == flatten_index
321
- # go deeper
322
- recordset, duplicate_data = flatten_hash(value, flatten_index, recordset, duplicate_data, flat_key)
323
- elsif flat_key_starts_with_flatten_index && !flatten_index_starts_with_flat_key
324
- # new record
325
- recordset << {parent_key.to_sym=>{key=>value}}
326
- elsif !flat_key_starts_with_flatten_index && flatten_index_starts_with_flat_key
327
- # go deeper
328
- recordset, duplicate_data = flatten_hash(value, flatten_index, recordset, duplicate_data, flat_key)
329
- elsif found_key
330
- # add to dup data for same level as flatten index
331
- duplicate_data.merge!(flat_key.to_sym=>value)
332
- else
333
- # add to dupe data
334
- duplicate_data.merge!(key=>value)
335
- end
336
- end # input.each
337
- when Array
338
- input.each do |value|
339
- recordset, duplicate_data = flatten_hash(value, flatten_index, recordset, duplicate_data, parent_key)
340
- end
341
- else
342
- recordset << {parent_key.to_sym=>input}
343
- end # case
344
- return recordset, duplicate_data
345
- end # flatten_hash
346
-
347
- # Creates an object with instance variables for each field at every level
348
- # This allows using a block like {:field1==true && :field2_subfield21="potato"}
349
- def create_object_from_flat_hash(record, hash_record=Class.new.new, parent_key=nil)
410
+ #puts "flat_key_starts_with_flatten_index: #{flat_key_starts_with_flatten_index}"
411
+ #puts "flatten_index_starts_with_flat_key: #{flatten_index_starts_with_flat_key}"
412
+
413
+ # figure out what we need to do based on where we're at in the record's value tree and man does it look ugly
414
+ if flat_key == flatten_index
415
+ # go deeper
416
+ recordset, duplicate_data = flatten_hash(value, flatten_index, recordset, duplicate_data, flat_key)
417
+ elsif flat_key_starts_with_flatten_index && !flatten_index_starts_with_flat_key
418
+ # new record
419
+ recordset << {parent_key.to_sym=>{key=>value}}
420
+ elsif !flat_key_starts_with_flatten_index && flatten_index_starts_with_flat_key
421
+ # go deeper
422
+ recordset, duplicate_data = flatten_hash(value, flatten_index, recordset, duplicate_data, flat_key)
423
+ elsif found_key
424
+ # add to dup data for same level as flatten index
425
+ duplicate_data.merge!(flat_key.to_sym=>value)
426
+ else
427
+ # add to dupe data
428
+ duplicate_data.merge!(key=>value)
429
+ end
430
+ end # input.each
431
+ when Array
432
+ input.each do |value|
433
+ recordset, duplicate_data = flatten_hash(value, flatten_index, recordset, duplicate_data, parent_key)
434
+ end
435
+ else
436
+ recordset << {parent_key.to_sym=>input}
437
+ end # case
438
+ return recordset, duplicate_data
439
+ end # flatten_hash
440
+
441
+ # Creates an object with instance variables for each field at every level
442
+ # This allows using a block like {:field1==true && :field2_subfield21="potato"}
443
+ def create_object_from_flat_hash(record, hash_record=Class.new.new, parent_key=nil)
444
+
445
+ # Iterate through the record creating the object recursively
446
+ case record
447
+ when Hash
448
+ record.each do |key, value|
449
+ flat_key = "#{parent_key}#{"__" if !parent_key.nil?}#{key}"
450
+ hash_record.instance_variable_set("@#{flat_key}", value)
451
+ hash_record = create_object_from_flat_hash(value, hash_record, flat_key)
452
+ end
453
+ when Array
454
+ record.each do |value|
455
+ hash_record = create_object_from_flat_hash(value, hash_record, parent_key)
456
+ end
457
+ else
458
+ hash_record.instance_variable_set("@#{parent_key}", record)
459
+ end # case
350
460
 
351
- # Iterate through the record creating the object recursively
352
- case record
353
- when Hash
354
- record.each do |key, value|
355
- flat_key = "#{parent_key}#{"_" if !parent_key.nil?}#{key}"
356
- hash_record.instance_variable_set("@#{flat_key}", value)
357
- hash_record = create_object_from_flat_hash(value, hash_record, flat_key)
358
- end
359
- when Array
360
- record.each do |value|
361
- hash_record = create_object_from_flat_hash(value, hash_record, parent_key)
362
- end
363
- else
364
- hash_record.instance_variable_set("@#{parent_key}", record)
365
- end # case
366
-
367
- hash_record
368
- end # create_object_from_flat_hash
369
-
370
- # Deal with the array methods allowing multiple functions to use the same code
371
- # You couldn't do this with alias because you can't tell what alias is used.
372
- #
373
- # My rule for using this vs a seperate method is if I can use the
374
- # same code for more than one method it goes in here, if the method
375
- # only works for one method then it gets its own method.
376
- def wrapper_method(method, *args, &block)
377
- # grab the raw data if it's a hashmodel
378
- case method
379
- when :[], :each_index, :uniq, :last, :collect, :length, :at, :map, :combination, :count, :cycle, :empty?, :fetch, :index, :first, :permutation, :size, :values_at
380
- flatten
381
- @modified_data.send(method, *args, &block)
382
- when :+, :*
383
- case args[0]
384
- when HashModel
385
- args = [args[0].raw_data]
386
- when Hash
387
- args = [args]
388
- end
389
- clone = self.clone
390
- clone.raw_data = clone.raw_data.send(method, *args, &block)
391
- clone.flatten
392
- else
393
- raise NoMethodError, "undefined method `#{method}' for #{self}"
394
- end
461
+ hash_record
462
+ end # create_object_from_flat_hash
463
+
464
+ # Deal with the array methods allowing multiple functions to use the same code
465
+ # You couldn't do this with alias because you can't tell what alias is used.
466
+ #
467
+ # My rule for using this vs a seperate method is if I can use the
468
+ # same code for more than one method it goes in here, if the method
469
+ # only works for one method then it gets its own method.
470
+ def wrapper_method(method, *args, &block)
471
+ # grab the raw data if it's a hashmodel
472
+ case method
473
+ when :[], :each_index, :uniq, :last, :collect, :length, :at, :map, :combination, :count, :cycle, :empty?, :fetch, :index, :first, :permutation, :size, :values_at
474
+ flatten
475
+ @modified_data.send(method, *args, &block)
476
+ when :+, :*
477
+ case args[0]
478
+ when HashModel
479
+ args = [args[0].raw_data]
480
+ when Hash
481
+ args = [args]
482
+ end
483
+ clone = self.clone
484
+ clone.raw_data = clone.raw_data.send(method, *args, &block)
485
+ clone.flatten
486
+ else
487
+ raise NoMethodError, "undefined method `#{method}' for #{self}"
395
488
  end
396
-
397
- # create methods like the given object so we can trap them
398
- def mimic_methods
399
- Array.new.public_methods(false).each do |method|
400
- # Don't mimic methods we specifically declare or methods that don't make sense for the class
401
- if !self.respond_to?(method)
402
- self.class.class_eval do
403
- define_method(method) { |*args, &block| wrapper_method(method, *args, &block) }
404
- end
489
+ end
490
+
491
+ # create methods like the given object so we can trap them
492
+ def mimic_methods
493
+ Array.new.public_methods(false).each do |method|
494
+ # Don't mimic methods we specifically declare or methods that don't make sense for the class
495
+ if !self.respond_to?(method)
496
+ self.class.class_eval do
497
+ define_method(method) { |*args, &block| wrapper_method(method, *args, &block) }
405
498
  end
406
499
  end
407
500
  end
501
+ end
408
502
 
409
- end # HashModel
410
-
411
- end # MikBe
503
+ end # HashModel