hashmodel 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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