hashmodel 0.3.1 → 0.4.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +13 -14
- data/README.markdown +63 -10
- data/hashmodel.gemspec +6 -0
- data/lib/hash_model/hash_model.rb +86 -116
- data/lib/hash_model/version.rb +3 -3
- data/lib/hashmodel.rb +2 -2
- data/spec/hash_model/_marshelling_spec.rb +4 -5
- data/spec/hash_model/hash_model_spec.rb +4 -987
- data/spec/spec_helper.rb +3 -28
- metadata +13 -13
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hashmodel (0.
|
4
|
+
hashmodel (0.4.0)
|
5
5
|
file-tail
|
6
6
|
sourcify
|
7
7
|
|
@@ -18,27 +18,28 @@ GEM
|
|
18
18
|
diff-lcs (1.1.2)
|
19
19
|
file-tail (1.0.5)
|
20
20
|
spruz (>= 0.1.0)
|
21
|
-
gherkin (2.3.
|
21
|
+
gherkin (2.3.4)
|
22
22
|
json (~> 1.4.6)
|
23
23
|
json (1.4.6)
|
24
|
-
rspec (2.
|
25
|
-
rspec-core (~> 2.
|
26
|
-
rspec-expectations (~> 2.
|
27
|
-
rspec-mocks (~> 2.
|
28
|
-
rspec-core (2.
|
29
|
-
rspec-expectations (2.
|
24
|
+
rspec (2.5.0)
|
25
|
+
rspec-core (~> 2.5.0)
|
26
|
+
rspec-expectations (~> 2.5.0)
|
27
|
+
rspec-mocks (~> 2.5.0)
|
28
|
+
rspec-core (2.5.1)
|
29
|
+
rspec-expectations (2.5.0)
|
30
30
|
diff-lcs (~> 1.1.2)
|
31
|
-
rspec-mocks (2.
|
31
|
+
rspec-mocks (2.5.0)
|
32
32
|
ruby2ruby (1.2.5)
|
33
33
|
ruby_parser (~> 2.0)
|
34
34
|
sexp_processor (~> 3.0)
|
35
|
-
ruby_parser (2.0.
|
35
|
+
ruby_parser (2.0.6)
|
36
36
|
sexp_processor (~> 3.0)
|
37
37
|
sexp_processor (3.0.5)
|
38
|
-
sourcify (0.4.
|
38
|
+
sourcify (0.4.2)
|
39
|
+
file-tail (>= 1.0.5)
|
39
40
|
ruby2ruby (>= 1.2.5)
|
40
41
|
sexp_processor (>= 3.0.5)
|
41
|
-
spruz (0.2.
|
42
|
+
spruz (0.2.5)
|
42
43
|
term-ansicolor (1.0.5)
|
43
44
|
|
44
45
|
PLATFORMS
|
@@ -46,7 +47,5 @@ PLATFORMS
|
|
46
47
|
|
47
48
|
DEPENDENCIES
|
48
49
|
cucumber
|
49
|
-
file-tail
|
50
50
|
hashmodel!
|
51
51
|
rspec
|
52
|
-
sourcify
|
data/README.markdown
CHANGED
@@ -6,7 +6,8 @@ It is not meant as a data storage device for managing huge datasets.
|
|
6
6
|
|
7
7
|
## Synopsis
|
8
8
|
|
9
|
-
The major usefulness of this class is it allows you to filter and search flattened records based on any field.
|
9
|
+
The major usefulness of this class is it allows you to filter and search flattened records based on any field. You can even updated and delete data now.
|
10
|
+
|
10
11
|
A field can contain anything, including another hash, a string, an array, or even an Object class like String or Array, not just an instance of an Object class.
|
11
12
|
|
12
13
|
Searches are very simple and logical. You can search using just using the value of the default index
|
@@ -29,11 +30,9 @@ Or more powerfully you can search using boolean like logic e.g.
|
|
29
30
|
|
30
31
|
## Status
|
31
32
|
|
32
|
-
2011.03.
|
33
|
-
|
34
|
-
## Developer Notes
|
33
|
+
2011.03.19 - Beta: 0.4.0.beta01
|
35
34
|
|
36
|
-
|
35
|
+
Lots of changes with this one. Mostly the ability to write to the HashModel and a few bug fixes. See Version History for details.
|
37
36
|
|
38
37
|
## Usage
|
39
38
|
|
@@ -216,7 +215,61 @@ I've covered most of the major stuff here but to see all of the functionality ta
|
|
216
215
|
>> {:parameter=>[{:type=>String}, "glorp", {:require=>true}], :switch=>[{:deep1=>{:deep3=>"deepTwo"}}, {:deep2=>"deepTwo"}, "--xtend"], :description=>"Xish stuff"}
|
217
216
|
|
218
217
|
|
219
|
-
## Version History
|
218
|
+
## Version History ##
|
219
|
+
|
220
|
+
0.4.0.beta01 - 2011.03-19
|
221
|
+
|
222
|
+
Lots of updates and code fixes for this release. After using it for a little while I've broken down and added the write functionality I was avoiding previously.
|
223
|
+
|
224
|
+
**Additions/Changes**
|
225
|
+
|
226
|
+
#### Methods: `update` and `update!` methods ####
|
227
|
+
These methods use a `where` like search that is slightly different. The methods look like this `update(default_index_search, field_new_value_hash, boolean_search_block)`. So if you search using a single value, a default index search, then you put the update hashes at the end. If you want to search using a boolean search then you put the update hashes at the beginning.
|
228
|
+
|
229
|
+
For instance:
|
230
|
+
|
231
|
+
# Default index search
|
232
|
+
my_hash_model.update("-x", :parameter__type=>Fixnum)
|
233
|
+
|
234
|
+
# Boolean search
|
235
|
+
my_hash_model.update(:parameter__type=>Fixnum) {:switch == "-x"}
|
236
|
+
|
237
|
+
You don't have to put in any search criteria at all though, you can just put in the field you want updated and it will update all records that are in the current filter set.
|
238
|
+
|
239
|
+
|
240
|
+
#### Methods: `update` and `update!` methods ####
|
241
|
+
Just like `update` and `update!` but will also add a hash if it doesn't exist already.
|
242
|
+
|
243
|
+
For instance if your HashModel has a record like `{:a=>"a"}` and you do `my_hash_model.update(:b=>"b")` it won't change that record, but it you do `my_hash_model.update_and_add(:b=>"b")` then your record will be `{:a=>'a', :b=>"b"}`.
|
244
|
+
|
245
|
+
|
246
|
+
#### Methods: `delete` and `delete!` ####
|
247
|
+
These use a the standard `where` type search used everywhere else. This function removes data from the raw data so if you have other flattened records that are based on that raw data they will no longer exist since the data that generated them is gone.
|
248
|
+
|
249
|
+
Just like an array these methods return the records they deleted.
|
250
|
+
|
251
|
+
#### Method: `parent` ####
|
252
|
+
Again this uses a `where` search and returns all raw parent records for flattened records matching the search criteria. (This method was there before but hadn't actually be coded, it was just a copy of some code I started but shouldn't have been in a release version).
|
253
|
+
|
254
|
+
#### Changed: `filter` ####
|
255
|
+
Filter has been changed from a property to a method. It is exactly like a `where` search but it's in-place and non-destructive. Since `where!` was changed to be truly destructive this change was needed. It also makes all the search functions identical in their usage.
|
256
|
+
|
257
|
+
#### Changed: `where!` ####
|
258
|
+
Changed to be truly destructive. If you run a where! on the class you're losing records that don't match it. The non-destructive version `where` is changed in that it does not contain the raw data of any records that don't match the `where` clause but the original HashModel remains untouched.
|
259
|
+
|
260
|
+
#### Removed: `group!` ####
|
261
|
+
Since bangs (!) are all now destructive, to bring the class inline with Ruby standards, it doesn't make logical sense to have a a group method that deletes all data except the search data then tries to find siblings; they were just deleted with the destructive call. Instead just use `group` and it will return the sibling records without touching the data in the HashModel.
|
262
|
+
|
263
|
+
#### Other changes ####
|
264
|
+
Because of the new destructive methods all input values will be cloned. You don't have to worry about cloning input objects yourself. If it's clonable HashModel will clone it.
|
265
|
+
|
266
|
+
I've reorganized the code into multiple files based on functionality to make it easier to debug when adding new features. This is in anticipation of a major cleanup and refactoring.
|
267
|
+
|
268
|
+
Cleaned up RSpecs a little along the lines of reorganization.
|
269
|
+
|
270
|
+
#### Bug fixes ####
|
271
|
+
* Threw error if you searched an empty HashModel (can't build a flatten index on nothing)
|
272
|
+
* Couldn't change the flatten index in some rare cases.
|
220
273
|
|
221
274
|
0.3.1 - 2011.03.18
|
222
275
|
|
@@ -253,7 +306,7 @@ e.g. hash_model.where{:x == "x" && :y == "y"} instead of the less natural hash_m
|
|
253
306
|
* Released on wrong RubyGems account (yanked)
|
254
307
|
|
255
308
|
|
256
|
-
##Contributing to HashModel
|
309
|
+
## Contributing to HashModel ##
|
257
310
|
|
258
311
|
* Pull requests are handled ASAP.
|
259
312
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
@@ -261,10 +314,10 @@ e.g. hash_model.where{:x == "x" && :y == "y"} instead of the less natural hash_m
|
|
261
314
|
* Fork the project
|
262
315
|
* Start a feature/bugfix branch
|
263
316
|
* Commit and push until you are happy with your contribution
|
264
|
-
* Make sure to add RSpecs in a separate file so I can easily tell what changed (changes without specs will not be pulled)
|
317
|
+
* Make sure to add RSpecs in a separate file so I can easily tell what changed (changes without specs will not be pulled).
|
265
318
|
* Changes to the configuration files, version numbers, or branches will not be pulled. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
266
319
|
|
267
|
-
##Copyright
|
320
|
+
## Copyright ##
|
268
321
|
|
269
|
-
Copyright (c)
|
322
|
+
Copyrighted free software - Copyright (c) 2011 Mike Bethany. See LICENSE.txt for further details.
|
270
323
|
|
data/hashmodel.gemspec
CHANGED
@@ -15,6 +15,12 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.add_dependency "sourcify"
|
16
16
|
s.add_dependency "file-tail"
|
17
17
|
|
18
|
+
# After hassling with version dependency it makes more sense NOT to
|
19
|
+
# declare version numbers and let it break if it is actually going
|
20
|
+
# to break instead of having it break every single time even if it
|
21
|
+
# wouldn't have broken with the new versions. Or maybe people need
|
22
|
+
# to be looser with their versioning requiremetns. My 2 cents.
|
23
|
+
#
|
18
24
|
s.add_development_dependency "rspec"
|
19
25
|
s.add_development_dependency "cucumber"
|
20
26
|
|
@@ -1,4 +1,11 @@
|
|
1
1
|
require 'sourcify'
|
2
|
+
require 'hash_model/hash_model_delete'
|
3
|
+
require 'hash_model/hash_model_group'
|
4
|
+
require 'hash_model/hash_model_filter'
|
5
|
+
require 'hash_model/hash_model_update'
|
6
|
+
require 'hash_model/hash_model_where'
|
7
|
+
|
8
|
+
|
2
9
|
# A simple MVC type model class for storing hashes as flattenable, searchable records
|
3
10
|
class HashModel
|
4
11
|
include Enumerable
|
@@ -11,12 +18,13 @@ class HashModel
|
|
11
18
|
mimic_methods
|
12
19
|
|
13
20
|
# Set values given as hashes
|
14
|
-
parameters.each { |key,value|
|
21
|
+
parameters.each { |key,value|
|
22
|
+
instance_variable_set("@#{key}", smart_clone(value)) }
|
15
23
|
|
16
24
|
# Allow a single hash to be added with :raw_data
|
17
25
|
@raw_data = [@raw_data] if @raw_data.class == Hash
|
18
26
|
|
19
|
-
check_field_names(@raw_data)
|
27
|
+
check_field_names(@raw_data) unless @raw_data.empty?
|
20
28
|
|
21
29
|
# Setup the flat data
|
22
30
|
flatten
|
@@ -25,12 +33,14 @@ class HashModel
|
|
25
33
|
|
26
34
|
## Properties
|
27
35
|
|
28
|
-
attr_accessor :flatten_index, :raw_data, :
|
29
|
-
|
36
|
+
attr_accessor :flatten_index, :raw_data, :filter_proc
|
37
|
+
|
30
38
|
# Sets field name used to flatten the recordset
|
31
39
|
def flatten_index=(value)
|
32
40
|
old_flatten = @flatten_index
|
41
|
+
old_filter = @filter_proc
|
33
42
|
@flatten_index = value
|
43
|
+
@filter_proc = nil
|
34
44
|
flatten
|
35
45
|
|
36
46
|
# Verify the flatten index is a valid index
|
@@ -41,6 +51,7 @@ class HashModel
|
|
41
51
|
|
42
52
|
unless flatten_found
|
43
53
|
@flatten_index = old_flatten
|
54
|
+
@filter_proc = old_filter
|
44
55
|
flatten
|
45
56
|
raise ArgumentError, "Flatten index could not be created: #{value}"
|
46
57
|
end
|
@@ -49,12 +60,7 @@ class HashModel
|
|
49
60
|
|
50
61
|
# Are the records being filtered?
|
51
62
|
def filtered?
|
52
|
-
!!@
|
53
|
-
end
|
54
|
-
|
55
|
-
def filter=(filter)
|
56
|
-
@filter = filter
|
57
|
-
flatten
|
63
|
+
!!@filter_proc
|
58
64
|
end
|
59
65
|
|
60
66
|
# Trap changes to raw data so we can re-flatten the data
|
@@ -62,11 +68,10 @@ class HashModel
|
|
62
68
|
value = [] if value.nil?
|
63
69
|
raise SyntaxError, "Raw data may only be an array of hashes" if value.class != Array
|
64
70
|
check_field_names(value)
|
65
|
-
@raw_data = value
|
71
|
+
@raw_data = smart_clone(value)
|
66
72
|
flatten
|
67
73
|
end
|
68
74
|
|
69
|
-
|
70
75
|
## Public Methods
|
71
76
|
|
72
77
|
# Freeze all the data properties
|
@@ -79,7 +84,7 @@ class HashModel
|
|
79
84
|
|
80
85
|
# Remove the in-place where filter
|
81
86
|
def clear_filter
|
82
|
-
@
|
87
|
+
@filter_proc = nil
|
83
88
|
flatten
|
84
89
|
end
|
85
90
|
alias :clear_where :clear_filter # in case this makes more sense to people
|
@@ -90,23 +95,25 @@ class HashModel
|
|
90
95
|
@modified_data = []
|
91
96
|
@unflatten_data = []
|
92
97
|
@flatten_index = nil
|
93
|
-
@
|
98
|
+
@filter_proc = nil
|
94
99
|
end
|
95
100
|
|
96
|
-
# Force internal arrays to be cloned
|
101
|
+
# Force internal arrays and variables to be cloned
|
97
102
|
def clone
|
103
|
+
return self if @raw_data.empty?
|
98
104
|
flatten
|
99
105
|
hm = HashModel.new(:raw_data=>@raw_data.clone)
|
100
106
|
hm.flatten_index = @flatten_index
|
101
|
-
hm.
|
107
|
+
hm.filter_proc = @filter_proc
|
102
108
|
hm
|
103
109
|
end
|
104
110
|
|
105
111
|
## Operators
|
106
112
|
|
107
|
-
# Overload Array#<< function so we can create the flatten index as the first record is added
|
108
|
-
#
|
113
|
+
# Overload Array#<< function so we can create the flatten index as the first record is added.
|
114
|
+
# This also allows us to send back this instance of the HashModel instead of an array.
|
109
115
|
def <<(value)
|
116
|
+
value = smart_clone(value)
|
110
117
|
case value
|
111
118
|
when HashModel
|
112
119
|
@raw_data.concat(value.raw_data)
|
@@ -116,8 +123,7 @@ class HashModel
|
|
116
123
|
check_field_names(value)
|
117
124
|
@raw_data << value
|
118
125
|
when Array
|
119
|
-
|
120
|
-
value.clone.each{ |member| self << member }
|
126
|
+
value.each{ |member| self << member }
|
121
127
|
else
|
122
128
|
raise SyntaxError, "You may only add a hash, another HashModel, or an array of either"
|
123
129
|
end
|
@@ -152,7 +158,6 @@ class HashModel
|
|
152
158
|
end
|
153
159
|
alias :eql? :==
|
154
160
|
|
155
|
-
|
156
161
|
# Remap spaceship to stop infinite loops
|
157
162
|
alias :_spaceship_ :<=>
|
158
163
|
private :_spaceship_
|
@@ -174,7 +179,6 @@ class HashModel
|
|
174
179
|
end
|
175
180
|
end
|
176
181
|
|
177
|
-
|
178
182
|
## Searching
|
179
183
|
|
180
184
|
# Tests flat or raw data depending of if you use flat or raw data
|
@@ -183,78 +187,17 @@ class HashModel
|
|
183
187
|
@modified_data.include?(value) || @raw_data.include?(value)
|
184
188
|
end
|
185
189
|
|
186
|
-
#
|
187
|
-
def
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
# Search the flattened records using the default flatten index or a boolean block
|
192
|
-
def where!(value=nil, &search)
|
193
|
-
# Parameter checks
|
194
|
-
raise SyntaxError, "You may only provide a parameter or a block but not both" if value && !search.nil?
|
195
|
-
|
196
|
-
# Allow clearing the filter and returning the entire recordset if nothing is given
|
197
|
-
if !value && search.nil?
|
198
|
-
@filter = nil
|
199
|
-
return flatten
|
200
|
-
end
|
201
|
-
|
202
|
-
# If given a parameter make our own search based on the flatten index
|
203
|
-
unless value.nil?
|
204
|
-
# Make sure the field name is available to the proc
|
205
|
-
case value
|
206
|
-
when String
|
207
|
-
string_search = ":#{@flatten_index} == \"#{value}\"".to_s
|
208
|
-
when Symbol
|
209
|
-
string_search = ":#{@flatten_index} == :#{value}".to_s
|
210
|
-
else
|
211
|
-
string_search = ":#{@flatten_index} == #{value}".to_s
|
212
|
-
end
|
213
|
-
else
|
214
|
-
# Convert the proc to a string so it can be viewed
|
215
|
-
# and later have :'s turned into @'s
|
216
|
-
|
217
|
-
# Sourcify can create single or multi-line procs so we have to make sure we deal with them accordingly
|
218
|
-
source = search.to_source
|
219
|
-
unless (match = source.match(/^proc do\n(.*)\nend$/))
|
220
|
-
match = source.match(/^proc { (.*) }$/)
|
221
|
-
end
|
222
|
-
string_search = match[1]
|
223
|
-
end # !value.nil?
|
224
|
-
|
225
|
-
# Set and process the filter
|
226
|
-
@filter = string_search
|
227
|
-
flatten
|
228
|
-
end
|
229
|
-
|
230
|
-
# Return the other records created from the same raw data record as the one(s) searched for
|
231
|
-
def group(value=nil, &search)
|
232
|
-
self.clone.group!(value, &search)
|
233
|
-
end
|
234
|
-
|
235
|
-
# Filter in place based on the parent record
|
236
|
-
def group!(value=nil, &search)
|
237
|
-
# Filter the recordset if applicable
|
238
|
-
if !value.nil? || !search.nil?
|
239
|
-
where!(value, &search)
|
240
|
-
end
|
241
|
-
# Get all the unique group id's
|
242
|
-
group_ids = @modified_data.collect {|hash| hash[:_group_id]}.uniq
|
243
|
-
self.filter = "#{group_ids.to_s}.include? :_group_id"
|
244
|
-
flatten
|
245
|
-
end
|
246
|
-
|
247
|
-
# Find the raw data record for a given flat record
|
248
|
-
def parent(flat_record)
|
249
|
-
flatten
|
250
|
-
@raw_data[flat_record[:_group_id]]
|
190
|
+
# Find the raw data record based on the search criteria
|
191
|
+
def parents(index_search=nil, &block_search)
|
192
|
+
flat_records = where(index_search, &block_search)
|
193
|
+
flat_records.collect{|flat| @raw_data[flat[:_group_id]]}.uniq
|
251
194
|
end
|
252
195
|
|
253
196
|
# Set the array value for self to the flattened hashes based on the flatten_index
|
254
197
|
def flatten
|
255
198
|
# Don't flatten the data if we don't need to
|
256
|
-
return self
|
257
|
-
|
199
|
+
return self unless dirty?
|
200
|
+
|
258
201
|
id = -1
|
259
202
|
group_id = -1
|
260
203
|
@modified_data.clear
|
@@ -262,6 +205,17 @@ class HashModel
|
|
262
205
|
@flatten_index = @raw_data[0].keys[0] if @raw_data != [] && @flatten_index.nil?
|
263
206
|
flatten_index = @flatten_index.to_s
|
264
207
|
|
208
|
+
|
209
|
+
# Change the filter so it looks for variables instead of symbols
|
210
|
+
unless @filter_proc.nil?
|
211
|
+
proc_filter = @filter_proc.clone
|
212
|
+
proc_filter.scan(/(:\S+) ==/).each {|match| proc_filter.sub!(match[0], match[0].sub(":","@"))}
|
213
|
+
proc_filter.sub!(":_group_id", "@_group_id")
|
214
|
+
proc_filter = "proc { #{proc_filter} }.call"
|
215
|
+
end
|
216
|
+
#dp "newfilter: #{proc_filter}"
|
217
|
+
|
218
|
+
|
265
219
|
# Flatten and filter the raw data
|
266
220
|
@raw_data.each do |record|
|
267
221
|
new_records, duplicate_data = flatten_hash(record, flatten_index)
|
@@ -272,18 +226,11 @@ class HashModel
|
|
272
226
|
# Double bangs aren't needed but are they more efficient?
|
273
227
|
new_record.merge!( duplicate_data.merge!( { :_id=>(id+=1), :_group_id=>group_id } ) )
|
274
228
|
end
|
275
|
-
|
276
|
-
# Change the filter so it looks for variables instead of symbols
|
277
|
-
unless @filter.nil?
|
278
|
-
proc_filter = @filter.clone
|
279
|
-
proc_filter.scan(/(:\S+) ==/).each {|match| proc_filter.sub!(match[0], match[0].sub(":","@"))}
|
280
|
-
proc_filter.sub!(":_group_id", "@_group_id")
|
281
|
-
proc_filter = "proc { #{proc_filter} }.call"
|
282
|
-
end
|
283
|
-
|
229
|
+
|
284
230
|
# Add the records to modified data if they pass the filter
|
285
231
|
new_records.each do |new_record|
|
286
|
-
|
232
|
+
#dp "rec: #{new_record}"
|
233
|
+
unless @filter_proc.nil?
|
287
234
|
flat = create_object_from_flat_hash(new_record)
|
288
235
|
@modified_data << new_record if flat.instance_eval proc_filter
|
289
236
|
else
|
@@ -310,42 +257,41 @@ class HashModel
|
|
310
257
|
# Return an array of the flattened data
|
311
258
|
def to_ary
|
312
259
|
flatten
|
313
|
-
@modified_data.to_ary
|
260
|
+
@modified_data.clone.to_ary
|
314
261
|
end
|
315
262
|
|
263
|
+
# Outputs the flattened data
|
316
264
|
def to_a
|
317
265
|
flatten
|
318
|
-
@modified_data.to_a
|
266
|
+
@modified_data.clone.to_a
|
319
267
|
end
|
320
268
|
|
321
269
|
# Iterate over the flattened records
|
322
270
|
def each
|
323
271
|
@modified_data.each do |record|
|
324
|
-
|
325
|
-
yield record
|
272
|
+
yield record
|
326
273
|
end
|
327
274
|
end
|
328
275
|
|
329
276
|
# Convert a flat record into an unflattened record
|
330
|
-
def unflatten(
|
331
|
-
HashModel.unflatten(
|
277
|
+
def unflatten(flat_record)
|
278
|
+
HashModel.unflatten(flat_record)
|
332
279
|
end
|
333
280
|
|
334
281
|
# Convert a flat record into an unflattened record
|
335
|
-
def self.unflatten(
|
282
|
+
def self.unflatten(flat_record)
|
336
283
|
# Seriously in need of a refactor, just looking at this hurts my brain
|
337
284
|
# There's a lot of redundancy here.
|
338
|
-
case
|
285
|
+
case flat_record
|
339
286
|
when Hash
|
340
287
|
new_record = {}
|
341
|
-
|
288
|
+
flat_record.each do |key, value|
|
342
289
|
# recursively look for flattened keys
|
343
290
|
keys = key.to_s.split("__", 2)
|
344
291
|
if keys[1]
|
345
292
|
key = keys[0].to_sym
|
346
293
|
value = unflatten({keys[1].to_sym => value})
|
347
294
|
end
|
348
|
-
|
349
295
|
# Don't overwrite existing value
|
350
296
|
if (existing = new_record[key])
|
351
297
|
# convert to array and search for subkeys if appropriate
|
@@ -382,11 +328,19 @@ class HashModel
|
|
382
328
|
new_record
|
383
329
|
when Array
|
384
330
|
# recurse into array
|
385
|
-
|
331
|
+
flat_record.collect! {|item| unflatten(item) }
|
386
332
|
else
|
387
|
-
|
333
|
+
flat_record
|
388
334
|
end
|
389
335
|
end
|
336
|
+
|
337
|
+
protected
|
338
|
+
|
339
|
+
# Allows access to the internal filter, needed to make sure clones filter properly
|
340
|
+
def filter_proc=(filter)
|
341
|
+
@filter_proc = filter
|
342
|
+
flatten
|
343
|
+
end
|
390
344
|
|
391
345
|
private
|
392
346
|
|
@@ -419,15 +373,15 @@ class HashModel
|
|
419
373
|
end
|
420
374
|
|
421
375
|
# Checks hash keys for reserved field names
|
422
|
-
def check_field_names(
|
423
|
-
case
|
376
|
+
def check_field_names(argument_list)
|
377
|
+
case argument_list
|
424
378
|
when Hash
|
425
|
-
|
379
|
+
argument_list.each do |key, value|
|
426
380
|
raise ReservedNameError, "use of reserved name :#{key} as a field name." if [:_id, :_group_id].include?(key)
|
427
381
|
check_field_names(value)
|
428
382
|
end
|
429
383
|
when Array
|
430
|
-
|
384
|
+
argument_list.clone.each { |record| check_field_names(record) }
|
431
385
|
end
|
432
386
|
end
|
433
387
|
|
@@ -439,7 +393,7 @@ class HashModel
|
|
439
393
|
# Create a hash based on internal values
|
440
394
|
def get_current_dirty_hash
|
441
395
|
# self.hash won't work
|
442
|
-
[@raw_data.hash, @
|
396
|
+
[@raw_data.hash, @filter_proc.hash, @flatten_index.hash].hash
|
443
397
|
end
|
444
398
|
|
445
399
|
# Recursively convert a single record into an array of new
|
@@ -563,4 +517,20 @@ class HashModel
|
|
563
517
|
end
|
564
518
|
end
|
565
519
|
|
520
|
+
# It's annoying to raise an error if an object can't
|
521
|
+
# be cloned, like in the case of symbols, It is much
|
522
|
+
# more friendly, and less surprising too, just to
|
523
|
+
# return the same object so you can go about your work.
|
524
|
+
# The only reason I clone is to protect the values, if
|
525
|
+
# the values don't need to be protected I don't want an
|
526
|
+
# annoying error message hosing up my whole day. </rant>
|
527
|
+
def smart_clone(object)
|
528
|
+
# Stupid error trapping
|
529
|
+
begin
|
530
|
+
object.clone
|
531
|
+
rescue
|
532
|
+
object
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
566
536
|
end # HashModel
|