hashmake 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.rdoc +12 -1
- data/README.rdoc +8 -6
- data/lib/hashmake/arg_spec.rb +3 -6
- data/lib/hashmake/hash_makeable.rb +130 -0
- data/lib/hashmake/version.rb +1 -1
- data/spec/hash_makeable_spec.rb +85 -3
- metadata +4 -4
data/ChangeLog.rdoc
CHANGED
@@ -20,4 +20,15 @@ In hash_make, add support for Hash containers which contain objects of type :typ
|
|
20
20
|
|
21
21
|
=== 0.1.4 / 2013-02-06
|
22
22
|
|
23
|
-
Add tests to verify array and hash container support.
|
23
|
+
Add tests to verify array and hash container support.
|
24
|
+
|
25
|
+
=== 0.1.5 / 2013-03-04
|
26
|
+
|
27
|
+
Hash arg specs to key name.
|
28
|
+
Instead of enumerating containers (e.g CONTAINER_HASH) just use class (Hash).
|
29
|
+
Validate args in validate_arg (which could be called independently from hash_make).
|
30
|
+
|
31
|
+
=== 0.1.6 / 2013-03-21
|
32
|
+
|
33
|
+
Add Hashmake::hash_makeable? for testing if a class includes Hashmake::HashMakeable.
|
34
|
+
Add #find_arg_specs and #make_hash methods to HashMakeable.
|
data/README.rdoc
CHANGED
@@ -2,13 +2,15 @@
|
|
2
2
|
|
3
3
|
* {Homepage}[https://rubygems.org/gems/hashmake]
|
4
4
|
* {Documentation}[http://rubydoc.info/gems/hashmake/frames]
|
5
|
-
* {Email}[mailto:jamestunnell
|
5
|
+
* {Email}[mailto:jamestunnell@lavabit.com]
|
6
6
|
|
7
7
|
== Description
|
8
8
|
|
9
9
|
Make hash-based object initialization easy!
|
10
10
|
|
11
|
-
Provides hash_make method that can consider parameter name, type, default value, validation, requiredd/not, and container type (none/array/hash) according to the specification provided.
|
11
|
+
Provides hash_make method that can consider parameter name, type, default value, validation, requiredd/not, and container type (none/array/hash) according to the specification provided. The value is assigned to an instance variable matching the arg key.
|
12
|
+
|
13
|
+
There is also a make_hash method to turn an object into a representative Hash object. Any hash-makeable sub-objects can be turned into Hash objects as well.
|
12
14
|
|
13
15
|
== Features
|
14
16
|
|
@@ -19,10 +21,10 @@ Provides hash_make method that can consider parameter name, type, default value,
|
|
19
21
|
class MyClass
|
20
22
|
include Hashmake::HashMakeable
|
21
23
|
|
22
|
-
ARG_SPECS =
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
ARG_SPECS = {
|
25
|
+
:x => arg_spec(:reqd => true, :type => Float, :validator => ->(a){ a.between?(0.0,1.0) }),
|
26
|
+
:y => arg_spec(:reqd => false, :type => Float, :validator => ->(a){ a.between?(0.0,1.0) }, :default => 0.0),
|
27
|
+
}
|
26
28
|
|
27
29
|
attr_reader :x, :y
|
28
30
|
|
data/lib/hashmake/arg_spec.rb
CHANGED
@@ -22,14 +22,12 @@ class ArgSpec
|
|
22
22
|
:type => Object,
|
23
23
|
}
|
24
24
|
|
25
|
-
attr_reader :
|
25
|
+
attr_reader :type, :validator, :reqd, :default, :container
|
26
26
|
|
27
27
|
# A new instance of ArgSpec.
|
28
28
|
#
|
29
|
-
# @param [Hash] hashed_args Hash to use in initializing an instance.
|
30
|
-
#
|
31
|
-
# :validator, :container, and :default.
|
32
|
-
# :key => the key used to identify a hashed arg.
|
29
|
+
# @param [Hash] hashed_args Hash to use in initializing an instance. Optional keys
|
30
|
+
# are :type, :reqd, :validator, :container, and :default.
|
33
31
|
# :type => the type of object expected to be paired
|
34
32
|
# with the key.
|
35
33
|
# :reqd => If true, the arg key must be in the hash
|
@@ -49,7 +47,6 @@ class ArgSpec
|
|
49
47
|
def initialize hashed_args
|
50
48
|
new_args = DEFAULT_ARGS.merge(hashed_args)
|
51
49
|
|
52
|
-
@key = new_args[:key]
|
53
50
|
@type = new_args[:type]
|
54
51
|
raise ArgumentError, "args[:type] #{@type} is not a Class" unless @type.is_a?(Class)
|
55
52
|
|
@@ -1,5 +1,10 @@
|
|
1
1
|
module Hashmake
|
2
2
|
|
3
|
+
# Determine if a class includes the HashMakeable module.
|
4
|
+
def self.hash_makeable? klass
|
5
|
+
klass.included_modules.include?(HashMakeable)
|
6
|
+
end
|
7
|
+
|
3
8
|
# This module should be included for any class that wants to be 'hash-makeable',
|
4
9
|
# which means that a new object instance expects all its arguments to come in a
|
5
10
|
# single Hash. See the hash_make method in this module and the ArgSpec class for
|
@@ -32,6 +37,30 @@ module HashMakeable
|
|
32
37
|
arg_specs.each do |key, arg_spec|
|
33
38
|
if hashed_args.has_key?(key)
|
34
39
|
val = hashed_args[key]
|
40
|
+
|
41
|
+
if Hashmake::hash_makeable?(arg_spec.type)
|
42
|
+
# If the val is not of the right type, but is a Hash, attempt to
|
43
|
+
# make an object of the right type if it is hash-makeable
|
44
|
+
if arg_spec.container == Array && val.is_a?(Array)
|
45
|
+
val.each_index do |i|
|
46
|
+
item = val[i]
|
47
|
+
if !item.is_a?(arg_spec.type) && item.is_a?(Hash)
|
48
|
+
val[i] = arg_spec.type.new item
|
49
|
+
end
|
50
|
+
end
|
51
|
+
elsif arg_spec.container == Hash && val.is_a?(Hash)
|
52
|
+
val.each_key do |item_key|
|
53
|
+
item = val[item_key]
|
54
|
+
if !item.is_a?(arg_spec.type) && item.is_a?(Hash)
|
55
|
+
val[item_key] = arg_spec.type.new item
|
56
|
+
end
|
57
|
+
end
|
58
|
+
else
|
59
|
+
if !val.is_a?(arg_spec.type) && val.is_a?(Hash)
|
60
|
+
val = arg_spec.type.new val
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
35
64
|
else
|
36
65
|
if arg_spec.reqd
|
37
66
|
raise ArgumentError, "hashed_args does not have required key #{key}"
|
@@ -51,6 +80,8 @@ module HashMakeable
|
|
51
80
|
end
|
52
81
|
end
|
53
82
|
|
83
|
+
# Check the given value, using the given ArgSpec object. An ArgumentError
|
84
|
+
# exception will be raised if the value is not valid.
|
54
85
|
def validate_arg arg_spec, val
|
55
86
|
if arg_spec.container == Array
|
56
87
|
raise ArgumentError, "val #{val} is not an array" unless val.is_a?(Array)
|
@@ -73,19 +104,118 @@ module HashMakeable
|
|
73
104
|
|
74
105
|
return true
|
75
106
|
end
|
107
|
+
|
108
|
+
# Look in the current class for a constant that is a Hash containing (only)
|
109
|
+
# ArgSpec objects. Returns the first constant matching this criteria, or nil
|
110
|
+
# if none was found.
|
111
|
+
def find_arg_specs
|
112
|
+
self.class.constants.each do |constant|
|
113
|
+
val = self.class.const_get(constant)
|
114
|
+
if val.is_a? Hash
|
115
|
+
all_arg_specs = true
|
116
|
+
val.each do |key,value|
|
117
|
+
unless value.is_a? ArgSpec
|
118
|
+
all_arg_specs = false
|
119
|
+
break
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if all_arg_specs
|
124
|
+
return val
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
return nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# Produce a hash that contains 'hashed args'. Each hashed arg is intended to
|
133
|
+
# be used in initializing an object instance.
|
134
|
+
#
|
135
|
+
# @param [Enumerable] arg_specs An enumerable of ArgSpec objects. Each one
|
136
|
+
# details an arg key that might be expected in the
|
137
|
+
# args hash. This param is nil by default. If the param
|
138
|
+
# is nil, this method will attempt to locate arg specs
|
139
|
+
# using find_arg_specs.
|
140
|
+
#
|
141
|
+
def make_hash arg_specs = nil
|
142
|
+
if arg_specs.nil?
|
143
|
+
arg_specs = self.find_arg_specs
|
144
|
+
raise "No arg specs given, and no class constant that is a Hash containing only ArgSpec objects was found" if arg_specs.nil?
|
145
|
+
end
|
146
|
+
|
147
|
+
arg_specs.each do |key, arg_spec|
|
148
|
+
raise ArgumentError, "arg_specs item #{arg_spec} is not a ArgSpec" unless arg_spec.is_a?(ArgSpec)
|
149
|
+
end
|
150
|
+
|
151
|
+
hash = {}
|
152
|
+
|
153
|
+
arg_specs.each do |key, arg_spec|
|
154
|
+
sym = "@#{key}".to_sym
|
155
|
+
raise ArgumentError, "current obj #{self} does not include instance variable #{sym}" if !self.instance_variables.include?(sym)
|
156
|
+
val = self.instance_variable_get(sym)
|
157
|
+
|
158
|
+
should_assign = false
|
76
159
|
|
160
|
+
if arg_spec.reqd
|
161
|
+
should_assign = true
|
162
|
+
else
|
163
|
+
if arg_spec.default.is_a?(Proc)
|
164
|
+
should_assign = (val != arg_spec.default.call)
|
165
|
+
else
|
166
|
+
should_assign = (val != arg_spec.default)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if should_assign
|
171
|
+
if val.is_a?(Array) && arg_spec.container == Array
|
172
|
+
ary = val
|
173
|
+
val = []
|
174
|
+
ary.each do |item|
|
175
|
+
if Hashmake::hash_makeable? item.class
|
176
|
+
val << item.make_hash
|
177
|
+
else
|
178
|
+
val << item
|
179
|
+
end
|
180
|
+
end
|
181
|
+
elsif val.is_a?(Hash) && arg_spec.container == Hash
|
182
|
+
hsh = val
|
183
|
+
val = {}
|
184
|
+
hsh.each do |hsh_key,item|
|
185
|
+
if Hashmake::hash_makeable? item.class
|
186
|
+
val[hsh_key] = item.make_hash
|
187
|
+
else
|
188
|
+
val[hsh_key] = item
|
189
|
+
end
|
190
|
+
end
|
191
|
+
elsif Hashmake::hash_makeable?(val.class)
|
192
|
+
val = val.make_hash
|
193
|
+
end
|
194
|
+
|
195
|
+
hash[key] = val
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
return hash
|
200
|
+
end
|
201
|
+
|
77
202
|
# Contains class methods to be added to a class that includes the
|
78
203
|
# HashMakeable module.
|
79
204
|
module ClassMethods
|
205
|
+
# Helper method to make a generic new ArgSpec object
|
80
206
|
def arg_spec args
|
81
207
|
ArgSpec.new args
|
82
208
|
end
|
83
209
|
|
210
|
+
# Helper method to make a ArgSpec object where the container is an Array.
|
211
|
+
# Set :default to a Proc that generates an empty array.
|
84
212
|
def arg_spec_array args
|
85
213
|
args = { :container => Array, :default => ->(){Array.new} }.merge(args)
|
86
214
|
ArgSpec.new args
|
87
215
|
end
|
88
216
|
|
217
|
+
# Helper method to make a ArgSpec object where the container is an Hash.
|
218
|
+
# Set :default to a Proc that generates an empty hash.
|
89
219
|
def arg_spec_hash args
|
90
220
|
args = { :container => Hash, :default => ->(){Hash.new} }.merge(args)
|
91
221
|
ArgSpec.new args
|
data/lib/hashmake/version.rb
CHANGED
data/spec/hash_makeable_spec.rb
CHANGED
@@ -2,17 +2,41 @@ require 'pry'
|
|
2
2
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
3
3
|
|
4
4
|
describe Hashmake::HashMakeable do
|
5
|
+
class AlsoHashMakeable
|
6
|
+
include Comparable
|
7
|
+
include HashMakeable
|
8
|
+
|
9
|
+
ARG_SPECS = {
|
10
|
+
:a_number => arg_spec(:reqd => false, :type => Numeric, :default => 1.0)
|
11
|
+
}
|
12
|
+
|
13
|
+
attr_accessor :a_number
|
14
|
+
|
15
|
+
def initialize args = {}
|
16
|
+
hash_make ARG_SPECS, args
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=>(other)
|
20
|
+
a_number <=> other.a_number
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
5
24
|
class MyTestClass
|
6
25
|
include HashMakeable
|
7
26
|
|
27
|
+
NON_REQD_FLOAT_DEFAULT = 0.0
|
28
|
+
|
8
29
|
ARG_SPECS = {
|
9
30
|
:reqd_string => arg_spec(:reqd => true, :type => String, :validator => ->(a){ a.length < 10 }),
|
10
|
-
:not_reqd_float => arg_spec(:reqd => false, :type => Float, :default =>
|
31
|
+
:not_reqd_float => arg_spec(:reqd => false, :type => Float, :default => NON_REQD_FLOAT_DEFAULT, :validator => ->(a){ a.between?(0.0,1.0) }),
|
11
32
|
:not_reqd_array_of_float => arg_spec_array(:reqd => false, :type => Float, :validator => ->(a){ a.between?(0.0,1.0) }),
|
12
33
|
:not_reqd_hash_of_float => arg_spec_hash(:reqd => false, :type => Float, :validator => ->(a){ a.between?(0.0,1.0) }),
|
34
|
+
:also_hash_makeable => arg_spec(:reqd => false, :type => AlsoHashMakeable, :default => ->(){ AlsoHashMakeable.new }),
|
35
|
+
:ary_of_also_hash_makeables => arg_spec_array(:reqd => false, :type => AlsoHashMakeable)
|
13
36
|
}
|
14
37
|
|
15
|
-
|
38
|
+
attr_accessor :not_reqd_float, :ary_of_also_hash_makeables
|
39
|
+
attr_reader :reqd_string, :not_reqd_array_of_float, :not_reqd_hash_of_float, :also_hash_makeable
|
16
40
|
|
17
41
|
def initialize hashed_args = {}
|
18
42
|
hash_make ARG_SPECS, hashed_args
|
@@ -104,6 +128,64 @@ describe Hashmake::HashMakeable do
|
|
104
128
|
end.should raise_error(ArgumentError)
|
105
129
|
end
|
106
130
|
end
|
131
|
+
|
132
|
+
context 'hash-makeable arg' do
|
133
|
+
it 'should construct the hash-makeable arg from just a Hash' do
|
134
|
+
a_number = 5
|
135
|
+
a = MyTestClass.new(:reqd_string => "ok", :also_hash_makeable => { :a_number => a_number })
|
136
|
+
a.also_hash_makeable.a_number.should eq(a_number)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#make_hash' do
|
142
|
+
before :each do
|
143
|
+
@reqd_string = "okeydoke"
|
144
|
+
@obj = MyTestClass.new :reqd_string => @reqd_string, :non_reqd_float => MyTestClass::NON_REQD_FLOAT_DEFAULT
|
145
|
+
@hash = @obj.make_hash
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should produce a Hash' do
|
149
|
+
@hash.should be_a Hash
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should always include req'd values" do
|
153
|
+
@hash.should include(:reqd_string)
|
154
|
+
@hash[:reqd_string].should eq(@reqd_string)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should never include non-req'd default values" do
|
158
|
+
@hash.should_not include(:non_reqd_float)
|
159
|
+
end
|
107
160
|
|
108
|
-
|
161
|
+
it "should always include non-req'd non-default values" do
|
162
|
+
@obj.not_reqd_float = 2.0
|
163
|
+
hash = @obj.make_hash
|
164
|
+
hash.should include(:not_reqd_float)
|
165
|
+
hash[:not_reqd_float].should eq(2.0)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should turn any hash-makeable objects into Hash objects" do
|
169
|
+
@obj.also_hash_makeable.a_number = 2.0
|
170
|
+
hash = @obj.make_hash
|
171
|
+
hash.should include(:also_hash_makeable)
|
172
|
+
hash[:also_hash_makeable].should be_a Hash
|
173
|
+
hash[:also_hash_makeable].should include(:a_number)
|
174
|
+
hash[:also_hash_makeable][:a_number].should eq(2.0)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should turn an array of hash-makeable objects into an array of Hash objects" do
|
178
|
+
obj2 = MyTestClass.new @obj.make_hash
|
179
|
+
@obj.ary_of_also_hash_makeables = [
|
180
|
+
AlsoHashMakeable.new(:a_number => 1),
|
181
|
+
AlsoHashMakeable.new(:a_number => 2),
|
182
|
+
AlsoHashMakeable.new(:a_number => 3),
|
183
|
+
]
|
184
|
+
obj2 = MyTestClass.new @obj.make_hash
|
185
|
+
obj2.ary_of_also_hash_makeables.count.should be(3)
|
186
|
+
obj2.ary_of_also_hash_makeables[0].a_number.should eq(1)
|
187
|
+
obj2.ary_of_also_hash_makeables[1].a_number.should eq(2)
|
188
|
+
obj2.ary_of_also_hash_makeables[2].a_number.should eq(3)
|
189
|
+
end
|
190
|
+
end
|
109
191
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hashmake
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -133,7 +133,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
133
|
version: '0'
|
134
134
|
segments:
|
135
135
|
- 0
|
136
|
-
hash:
|
136
|
+
hash: 77734045
|
137
137
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
138
|
none: false
|
139
139
|
requirements:
|
@@ -142,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
142
|
version: '0'
|
143
143
|
segments:
|
144
144
|
- 0
|
145
|
-
hash:
|
145
|
+
hash: 77734045
|
146
146
|
requirements: []
|
147
147
|
rubyforge_project:
|
148
148
|
rubygems_version: 1.8.23
|