hashmodel 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -10
- data/Gemfile.lock +13 -14
- data/README.markdown +11 -2
- data/Rakefile +3 -8
- data/_brainstorm/hash_test.rb +169 -0
- data/_brainstorm/instance_vars.rb +24 -0
- data/_brainstorm/ref_val.rb +16 -0
- data/_brainstorm/spliting.rb +46 -0
- data/_brainstorm/test.rb +27 -0
- data/features/README +1 -1
- data/features/hash_model_flatten.feature +5 -5
- data/features/hash_model_search.feature +2 -2
- data/features/step_definitions/hash_model_steps.rb +2 -2
- data/lib/hash_model/exceptions.rb +1 -1
- data/lib/hash_model/hash_model.rb +455 -363
- data/lib/hash_model/version.rb +9 -11
- data/spec/hash_model/hash_model_spec.rb +216 -119
- metadata +14 -18
data/Gemfile
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
5
2
|
|
6
|
-
# Add dependencies to develop your gem here.
|
7
|
-
# Include everything needed to run rake, tests, features, etc.
|
8
3
|
group :development do
|
9
|
-
gem "rspec"
|
10
|
-
gem "cucumber"
|
11
|
-
gem "bundler"
|
12
|
-
gem "jeweler"
|
13
|
-
gem "rcov"
|
4
|
+
gem "rspec"
|
5
|
+
gem "cucumber"
|
6
|
+
gem "bundler"
|
7
|
+
gem "jeweler"
|
8
|
+
gem "rcov"
|
14
9
|
end
|
data/Gemfile.lock
CHANGED
@@ -9,33 +9,32 @@ GEM
|
|
9
9
|
json (~> 1.4.6)
|
10
10
|
term-ansicolor (~> 1.0.5)
|
11
11
|
diff-lcs (1.1.2)
|
12
|
-
gherkin (2.3.
|
12
|
+
gherkin (2.3.3)
|
13
13
|
json (~> 1.4.6)
|
14
|
-
term-ansicolor (~> 1.0.5)
|
15
14
|
git (1.2.5)
|
16
|
-
jeweler (1.5.
|
15
|
+
jeweler (1.5.2)
|
17
16
|
bundler (~> 1.0.0)
|
18
17
|
git (>= 1.2.5)
|
19
18
|
rake
|
20
19
|
json (1.4.6)
|
21
20
|
rake (0.8.7)
|
22
21
|
rcov (0.9.9)
|
23
|
-
rspec (2.
|
24
|
-
rspec-core (~> 2.
|
25
|
-
rspec-expectations (~> 2.
|
26
|
-
rspec-mocks (~> 2.
|
27
|
-
rspec-core (2.
|
28
|
-
rspec-expectations (2.
|
22
|
+
rspec (2.4.0)
|
23
|
+
rspec-core (~> 2.4.0)
|
24
|
+
rspec-expectations (~> 2.4.0)
|
25
|
+
rspec-mocks (~> 2.4.0)
|
26
|
+
rspec-core (2.4.0)
|
27
|
+
rspec-expectations (2.4.0)
|
29
28
|
diff-lcs (~> 1.1.2)
|
30
|
-
rspec-mocks (2.
|
29
|
+
rspec-mocks (2.4.0)
|
31
30
|
term-ansicolor (1.0.5)
|
32
31
|
|
33
32
|
PLATFORMS
|
34
33
|
ruby
|
35
34
|
|
36
35
|
DEPENDENCIES
|
37
|
-
bundler
|
38
|
-
cucumber
|
39
|
-
jeweler
|
36
|
+
bundler
|
37
|
+
cucumber
|
38
|
+
jeweler
|
40
39
|
rcov
|
41
|
-
rspec
|
40
|
+
rspec
|
data/README.markdown
CHANGED
@@ -21,11 +21,20 @@ found = @hm.where {@switch == "-x" && @parameter\_type == String}
|
|
21
21
|
|
22
22
|
## Usage
|
23
23
|
|
24
|
-
Coming soon...
|
25
24
|
|
26
|
-
For now take a look at the spec files to see simple examples of how to use the HashModel
|
27
25
|
|
26
|
+
## Version History
|
28
27
|
|
28
|
+
0.2.0
|
29
|
+
* Fixed bug if first field name is shorter version of another field name, e.g. :short then :shorter would cause an error.
|
30
|
+
* Added unflattening records and adding unflattened records.
|
31
|
+
* Changed field separator to double underscores (to allow unflattening)
|
32
|
+
* Removed namespace module, it was annoying. Now just instantiate it with HashModel.new instead of MikBe::HashModel.new
|
33
|
+
* Now allows a single hash, instead of an array of hashes, when creating with HashModel.new(:raw_data => hash)
|
34
|
+
|
35
|
+
0.1.1 Moved to new RubyGems account
|
36
|
+
|
37
|
+
0.1.0 Initial publish
|
29
38
|
|
30
39
|
== Contributing to hash\_model
|
31
40
|
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ rescue Bundler::BundlerError => e
|
|
9
9
|
end
|
10
10
|
require 'rake'
|
11
11
|
require './lib/hash_model/version'
|
12
|
-
version =
|
12
|
+
version = HashModel::VERSION::STRING
|
13
13
|
|
14
14
|
require 'jeweler'
|
15
15
|
Jeweler::Tasks.new do |gem|
|
@@ -25,19 +25,14 @@ Jeweler::RubygemsDotOrgTasks.new
|
|
25
25
|
|
26
26
|
require 'rspec/core'
|
27
27
|
require 'rspec/core/rake_task'
|
28
|
-
RSpec::Core::RakeTask.new(:
|
28
|
+
RSpec::Core::RakeTask.new(:rspec) do |spec|
|
29
29
|
spec.pattern = FileList['spec/**/*_spec.rb']
|
30
30
|
end
|
31
31
|
|
32
|
-
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
33
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
-
spec.rcov = true
|
35
|
-
end
|
36
|
-
|
37
32
|
require 'cucumber/rake/task'
|
38
33
|
Cucumber::Rake::Task.new(:features)
|
39
34
|
|
40
|
-
task :default => :
|
35
|
+
task :default => :rspec
|
41
36
|
|
42
37
|
require 'rake/rdoctask'
|
43
38
|
Rake::RDocTask.new do |rdoc|
|
@@ -0,0 +1,169 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), "/../lib"))
|
2
|
+
require 'hash_model'
|
3
|
+
|
4
|
+
hash = {
|
5
|
+
:field1 => "f",
|
6
|
+
:field2 => {
|
7
|
+
:field3 => "f3",
|
8
|
+
:field4 => {
|
9
|
+
:field5 => "f5",
|
10
|
+
:field6 => ["f6", "f7"]
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
=begin
|
15
|
+
|
16
|
+
hash2 = {
|
17
|
+
:switch => ["-x", "--xtend"],
|
18
|
+
:parameter => {:type => String, :require => true},
|
19
|
+
:description => "Xish stuff",
|
20
|
+
:field => {:field2 => {:field3 => "ff3", :field4 => "ff4"}}
|
21
|
+
}
|
22
|
+
|
23
|
+
hash2 = {
|
24
|
+
:switch => ["-x", "--xtend"],
|
25
|
+
:parameter => {:type => String, :require => true},
|
26
|
+
:description => "Xish stuff",
|
27
|
+
:field => {:field2 => [:field3 => "ff3", :field4 => "ff4", "ff5"]}
|
28
|
+
}
|
29
|
+
|
30
|
+
|
31
|
+
hm = HashModel.new
|
32
|
+
hm << hash2
|
33
|
+
hm.flatten_index = :field__field2
|
34
|
+
|
35
|
+
|
36
|
+
flat = {
|
37
|
+
:field2__field4__field5=>"f5",
|
38
|
+
:field1=>"f",
|
39
|
+
:field3=>"f3",
|
40
|
+
:field2__field4__field6=>["f6", "f7"]
|
41
|
+
}
|
42
|
+
|
43
|
+
[
|
44
|
+
{
|
45
|
+
:field__field2__field3=>"ff3",
|
46
|
+
:field__field2__field4=>"ff4",
|
47
|
+
:switch=>["-x", "--xtend"],
|
48
|
+
:parameter__type=>String,
|
49
|
+
:parameter__require=>true,
|
50
|
+
:description=>"Xish stuff",
|
51
|
+
}
|
52
|
+
]
|
53
|
+
|
54
|
+
puts "\nhm: #{hm}"
|
55
|
+
#hash2 = {:field => "field", :field2__field3 => "field3", :field2__field4 => {:field5 => "field5", :field6 => ["field6", "field7"]}}}
|
56
|
+
=end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
def unflatten(input)
|
61
|
+
# Seriously in need of a refactor, just looking at this hurts my brain
|
62
|
+
case input
|
63
|
+
when Hash
|
64
|
+
new_record = {}
|
65
|
+
input.each do |key, value|
|
66
|
+
puts "#{key} => #{value}"
|
67
|
+
# recursively look for flattened keys
|
68
|
+
keys = key.to_s.split("__", 2)
|
69
|
+
if keys[1]
|
70
|
+
key = keys[0].to_sym
|
71
|
+
value = unflatten({keys[1].to_sym => value})
|
72
|
+
end
|
73
|
+
|
74
|
+
# Don't overwrite existing value
|
75
|
+
if (existing = new_record[key])
|
76
|
+
# convert to array and search for subkeys if appropriate
|
77
|
+
if existing.class == Hash
|
78
|
+
# Convert to an array if something other than a hash is added
|
79
|
+
unless value.class == Hash
|
80
|
+
new_record[key] = hash_to_array(existing)
|
81
|
+
new_record[key] << value
|
82
|
+
else
|
83
|
+
# Search subkeys for duplicate values if it's a hash
|
84
|
+
unless (found_keys = existing.keys & value.keys).empty?
|
85
|
+
found_keys.each do |found_key|
|
86
|
+
if new_record[key][found_key].class == Hash
|
87
|
+
unless value[found_key].class == Hash
|
88
|
+
new_record[key] = hash_to_array(new_record[key][found_key])
|
89
|
+
new_record[key] << value[found_key]
|
90
|
+
else
|
91
|
+
new_record[key][found_key].merge!(value[found_key])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
else
|
96
|
+
new_record[key].merge!(value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
else
|
100
|
+
new_record[key] << value
|
101
|
+
end
|
102
|
+
else
|
103
|
+
new_record.merge!(key => value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
new_record
|
107
|
+
when Array
|
108
|
+
# recurse into array
|
109
|
+
input.collect! {|item| unflatten(item) }
|
110
|
+
else
|
111
|
+
input
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def hash_to_array(hash)
|
117
|
+
array = []
|
118
|
+
hash.each do |key, value|
|
119
|
+
array << {key => value}
|
120
|
+
end
|
121
|
+
array
|
122
|
+
end
|
123
|
+
|
124
|
+
hash = [
|
125
|
+
{
|
126
|
+
:field__field2__field3=>"ff3",
|
127
|
+
:field__field2__field4=>"ff4"
|
128
|
+
}
|
129
|
+
]
|
130
|
+
|
131
|
+
hash2 = {
|
132
|
+
:switch=>["-x", "--xtend"],
|
133
|
+
:parameter__type=>String,
|
134
|
+
:parameter__require=>true,
|
135
|
+
:description=>"Xish stuff",
|
136
|
+
}
|
137
|
+
|
138
|
+
|
139
|
+
hash3 = {
|
140
|
+
:switch=>[{:deep1 => "deepOne"}, {:deep2 => "deepTwo"}, "--xtend"],
|
141
|
+
:parameter__type=>String,
|
142
|
+
:parameter__require=>true,
|
143
|
+
:description=>"Xish stuff",
|
144
|
+
}
|
145
|
+
|
146
|
+
|
147
|
+
hash4 = {
|
148
|
+
:parameter__type=>String,
|
149
|
+
:switch__deep1__deep3 => "deepTwo",
|
150
|
+
:parameter__type__ruby=>true,
|
151
|
+
:parameter => "glorp",
|
152
|
+
:parameter__require=>true,
|
153
|
+
:switch__deep2 => "deepTwo",
|
154
|
+
:description=>"Xish stuff",
|
155
|
+
:switch => "--xtend",
|
156
|
+
}
|
157
|
+
|
158
|
+
|
159
|
+
unflat = unflatten(hash4)
|
160
|
+
puts "\nUnflat: #{unflat}"
|
161
|
+
|
162
|
+
=begin
|
163
|
+
puts "to_a: #{hash2.to_a}"
|
164
|
+
|
165
|
+
x = [1,2,3]
|
166
|
+
y = [3,4,5]
|
167
|
+
|
168
|
+
puts "\nx & y = #{x & y}"
|
169
|
+
=end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Foo
|
2
|
+
|
3
|
+
attr_accessor :bar
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@bar = 1
|
7
|
+
end
|
8
|
+
|
9
|
+
def show
|
10
|
+
vars = instance_variables
|
11
|
+
instance_variables.each do |var|
|
12
|
+
puts "var: #{var}"
|
13
|
+
end
|
14
|
+
print "no vars "
|
15
|
+
puts instance_variables.class
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
f = Foo.new
|
21
|
+
|
22
|
+
f.show
|
23
|
+
|
24
|
+
puts f.instance_variables
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
def deflatten(input)
|
3
|
+
case input
|
4
|
+
when Hash
|
5
|
+
new_hash = {}
|
6
|
+
input.each do |key,value|
|
7
|
+
split_key = key.to_s.split("__",2)
|
8
|
+
new_hash_key = split_key[0].to_sym
|
9
|
+
if split_key.length > 1
|
10
|
+
child_hash = {split_key[1].to_sym => value}
|
11
|
+
value = deflatten(child_hash)
|
12
|
+
end
|
13
|
+
|
14
|
+
#look for existing keys so we don't overwrite them
|
15
|
+
existing_value = new_hash[new_hash_key]
|
16
|
+
if !existing_value.nil?
|
17
|
+
if existing_value.class == Hash && value.class == Hash
|
18
|
+
value = existing_value.merge(value)
|
19
|
+
elsif existing_value.class == Array
|
20
|
+
value = existing_value << value
|
21
|
+
else
|
22
|
+
value = [value, existing_value]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
new_hash.merge!(new_hash_key => value)
|
26
|
+
end
|
27
|
+
new_hash
|
28
|
+
when Array
|
29
|
+
input.collect { |value| deflatten(value.clone)}
|
30
|
+
else
|
31
|
+
input
|
32
|
+
end # case
|
33
|
+
end
|
34
|
+
|
35
|
+
hash = {:switch=>"-x", :parameter__type=>String, :parameter__required=>true, :some__value__blrop=>[1,2,3], :some__hash=>{:blah=>"bloo", :bleep=>4}, :some__value=>"something", :some__others=>"others", :some__array=> [1,2,3], :description=>"the x paramemter"}
|
36
|
+
|
37
|
+
puts ""
|
38
|
+
puts ""
|
39
|
+
puts "build_hash: #{deflatten(hash)}"
|
40
|
+
|
41
|
+
{
|
42
|
+
:switch=>"-x",
|
43
|
+
:parameter=>{:required=>true, :type=>String},
|
44
|
+
:some=>{:array=>[1, 2, 3], :others=>"others", :value=>[{:blrop=>[1,2,3]}, "something"], :hash=>{:blah=>"bloo", :bleep=>4}},
|
45
|
+
:description=>"the x paramemter"
|
46
|
+
}
|
data/_brainstorm/test.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class Fruit
|
2
|
+
|
3
|
+
def set_defaults
|
4
|
+
@color ||= 'green'
|
5
|
+
@type ||= 'pear'
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(params = {})
|
9
|
+
params.each { |key,value| instance_variable_set("@#{key}", value) }
|
10
|
+
set_defaults
|
11
|
+
instance_variables.each {|var| self.class.send(:attr_accessor, var)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
instance_variables.inject("") {|vars, var| vars += "#{var}: #{instance_variable_get(var)}; "}
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
puts Fruit.new
|
21
|
+
puts Fruit.new :color => 'red', :type => 'grape'
|
22
|
+
puts Fruit.new :type => 'pomegranate'
|
23
|
+
puts Fruit.new :cost => 20.21
|
24
|
+
puts Fruit.new :foo => "bar"
|
25
|
+
|
26
|
+
f = Fruit.new :potato => "salad"
|
27
|
+
puts "f.cost.nil? #{f.cost.nil?}"
|
data/features/README
CHANGED
@@ -2,7 +2,7 @@ These features really only make sense to write when I'm trying to figure out wha
|
|
2
2
|
functionality I want to add. When I already know it just seems redundant to do it
|
3
3
|
here since this app is such a low level library.
|
4
4
|
|
5
|
-
The Cucumber methodologies don't seem to be necessary most of the time since
|
5
|
+
The Cucumber methodologies don't seem to be necessary most of the time since
|
6
6
|
RSpec seems to be a more natural fit for a programming library. I'm probably
|
7
7
|
wrong but it really does seem to be a lot of redundancy for this application.
|
8
8
|
|
@@ -27,8 +27,8 @@ Scenario: Flatten input hashes to the default flatten index
|
|
27
27
|
When the HashModel is populated with the test table
|
28
28
|
Then the HashModel recordset should look like
|
29
29
|
| id | group_id | switch | description |
|
30
|
-
| :
|
31
|
-
| :
|
32
|
-
| :
|
33
|
-
| :
|
34
|
-
| :
|
30
|
+
| :_id=>0 | :_group_id=> 0 | :switch=>"-x" | :description=>"This is a description" |
|
31
|
+
| :_id=>1 | :_group_id=> 0 | :switch=>"--xtended" | :description=>"This is a description" |
|
32
|
+
| :_id=>2 | :_group_id=> 1 | :switch=>"-y" | :description=>"Why not?" |
|
33
|
+
| :_id=>3 | :_group_id=> 2 | :switch=>"-z" | :description=>"head for zee hills" |
|
34
|
+
| :_id=>4 | :_group_id=> 2 | :switch=>"--zee" | :description=>"head for zee hills" |
|
@@ -17,7 +17,7 @@ Scenario: Search using a parameter
|
|
17
17
|
When we search with the single parameter "-x"
|
18
18
|
Then the search recordset should look like
|
19
19
|
| id | group_id | switch | description |
|
20
|
-
| :
|
20
|
+
| :_id=>0 | :_group_id=> 0 | :switch=>"-x" | :description=>"This is a description" |
|
21
21
|
|
22
22
|
@active
|
23
23
|
Scenario: Search using a block of boolean logic
|
@@ -26,7 +26,7 @@ Scenario: Search using a block of boolean logic
|
|
26
26
|
When we search with the block {@switch == "-x"}
|
27
27
|
Then the search recordset should look like
|
28
28
|
| id | group_id | switch | description |
|
29
|
-
| :
|
29
|
+
| :_id=>0 | :_group_id=> 0 | :switch=>"-x" | :description=>"This is a description" |
|
30
30
|
|
31
31
|
|
32
32
|
|