hashmake 0.1.5 → 0.1.6
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/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
|