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.
- 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
|
|