bound 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +2 -3
- data/Rakefile +0 -1
- data/benchmark.rb +201 -26
- data/bound.gemspec +1 -0
- data/lib/bound.rb +222 -214
- data/lib/bound/version.rb +1 -1
- data/spec/bound_spec.rb +0 -55
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb1d1bdcd67442312ce8e48fa04a1e202aca15a2
|
4
|
+
data.tar.gz: 1a506e8c642e84938a4f32ae8f97cf2ed579b05b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa1024912a23354e5bb1ec42ee3518b6b2de5e5e8229eb2182c93f8b69783dc5dfc16e03d0f5da3d6f525ed4472b028f5787952848cc04795728786223df3cf3
|
7
|
+
data.tar.gz: a27f0003a78e6ad4fbc69362cb6da84707c6b5f5887e900cec0eccf0f23879b1bdf6cc393e4b7313c4e7644827e1faee1c42745bcbc0eb455e89f58c1534df5a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
data/benchmark.rb
CHANGED
@@ -2,10 +2,102 @@ $: << 'lib'
|
|
2
2
|
require 'bound'
|
3
3
|
require 'benchmark'
|
4
4
|
|
5
|
-
|
5
|
+
if ENV['PROFILE']
|
6
|
+
require 'perftools'
|
7
|
+
def start_perf(name)
|
8
|
+
@_perf_name_ = 'prof__' + name
|
9
|
+
PerfTools::CpuProfiler.start @_perf_name_
|
10
|
+
end
|
11
|
+
|
12
|
+
def finish_perf
|
13
|
+
PerfTools::CpuProfiler.stop
|
14
|
+
system "pprof.rb --pdf #@_perf_name_ > #{@_perf_name_}.pdf"
|
15
|
+
system "rm -f ./#{@_perf_name_} ./#{@_perf_name_}.symbols"
|
16
|
+
ensure
|
17
|
+
@_perf_name_ = nil
|
18
|
+
end
|
19
|
+
else
|
20
|
+
def start_perf(*);end
|
21
|
+
def finish_perf(*);end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
TestBoundary = Bound.required(
|
27
|
+
:foo,
|
28
|
+
:bar => [Bound.required(:abc)],
|
29
|
+
:baz => Bound.required(:gonzo)
|
30
|
+
)
|
31
|
+
|
32
|
+
StructBoundary = Struct.new(:foo, :bar, :baz)
|
33
|
+
BarStructBoundary = Struct.new(:abc)
|
34
|
+
BazStructBoundary = Struct.new(:gonzo)
|
35
|
+
|
36
|
+
StaticBoundClass = Class.new do
|
37
|
+
def self.initialize_unvalidated
|
38
|
+
class_eval <<-EOR
|
39
|
+
def initialize(target, overwrite = nil)
|
40
|
+
@t, @o = target, overwrite
|
41
|
+
end
|
42
|
+
EOR
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.define_delegate(attr, prefix = '')
|
46
|
+
class_eval <<-EOR
|
47
|
+
def #{prefix}#{attr}
|
48
|
+
return @o[:#{attr}] if @o && @o.key?(:#{attr})
|
49
|
+
@t.kind_of?(Hash)? @t[:#{attr}] : @t.#{attr}
|
50
|
+
end
|
51
|
+
EOR
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.define_nested_delegate(attr, nested_class)
|
55
|
+
define_delegate attr, 'get_'
|
56
|
+
if nested_class.kind_of? Array
|
57
|
+
nested_class = nested_class.first
|
58
|
+
class_eval <<-EOR
|
59
|
+
def #{attr}
|
60
|
+
@#{attr} ||= get_#{attr}.map{|t| #{nested_class}.new t}
|
61
|
+
end
|
62
|
+
private :get_#{attr}
|
63
|
+
EOR
|
64
|
+
else
|
65
|
+
class_eval <<-EOR
|
66
|
+
def #{attr}
|
67
|
+
@#{attr} ||= #{nested_class}.new(get_#{attr})
|
68
|
+
end
|
69
|
+
private :get_#{attr}
|
70
|
+
EOR
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
BarStaticBoundary = Class.new(StaticBoundClass) do
|
76
|
+
initialize_unvalidated
|
77
|
+
|
78
|
+
define_delegate :abc
|
79
|
+
end
|
80
|
+
|
81
|
+
BazStaticBoundary = Class.new(StaticBoundClass) do
|
82
|
+
initialize_unvalidated
|
83
|
+
|
84
|
+
define_delegate :gonzo
|
85
|
+
end
|
86
|
+
|
87
|
+
StaticBoundary = Class.new(StaticBoundClass) do
|
88
|
+
initialize_unvalidated
|
89
|
+
|
90
|
+
define_delegate :foo
|
91
|
+
define_nested_delegate :bar, [BarStaticBoundary]
|
92
|
+
define_nested_delegate :baz, BazStaticBoundary
|
93
|
+
end
|
6
94
|
|
7
|
-
|
8
|
-
|
95
|
+
def assert_correctness(bound)
|
96
|
+
raise('foo is wrong') unless bound.foo == 'NOPE'
|
97
|
+
raise('bar size is wrong') unless bound.bar.size == 2
|
98
|
+
raise('bar[0] is wrong') unless bound.bar[0].abc == 'TRUE'
|
99
|
+
raise('bar[1] is wrong') unless bound.bar[1].abc == 'FALSE'
|
100
|
+
raise('baz.gonzo is wrong') unless bound.baz.gonzo == 22
|
9
101
|
end
|
10
102
|
|
11
103
|
def bench(key, &block)
|
@@ -17,36 +109,119 @@ def bench(key, &block)
|
|
17
109
|
result
|
18
110
|
end
|
19
111
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
112
|
+
Provider = Class.new do
|
113
|
+
attr_accessor :foo, :bar, :baz
|
114
|
+
BarProvider = Class.new do
|
115
|
+
attr_accessor :abc
|
116
|
+
end
|
117
|
+
BazProvider = Class.new do
|
118
|
+
attr_accessor :gonzo
|
119
|
+
end
|
120
|
+
end
|
121
|
+
provider_objects = 100_000.times.map do |i|
|
122
|
+
Provider.new.tap do |p|
|
123
|
+
p.foo = 'YES'
|
124
|
+
p.bar = [
|
125
|
+
BarProvider.new.tap do |brp|
|
126
|
+
brp.abc = 'TRUE'
|
127
|
+
end,
|
128
|
+
BarProvider.new.tap do |brp|
|
129
|
+
brp.abc = 'FALSE'
|
130
|
+
end
|
131
|
+
]
|
132
|
+
p.baz = BazProvider.new.tap do |bzp|
|
133
|
+
bzp.gonzo = 22
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
provider_hashes = 100_000.times.map do |i|
|
139
|
+
{
|
140
|
+
:foo => 'YES',
|
141
|
+
:bar => [{:abc => 'TRUE'}, {:abc => 'FALSE'}],
|
142
|
+
:baz => {:gonzo => 22}
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
overwrite = {:foo => 'NOPE'}
|
147
|
+
|
148
|
+
|
149
|
+
start_perf 'bound.objt'
|
150
|
+
bench ' bound w/ objt' do
|
151
|
+
provider_objects.each do |provider|
|
152
|
+
result = TestBoundary.new(provider, overwrite)
|
153
|
+
assert_correctness result
|
154
|
+
end
|
155
|
+
end
|
156
|
+
finish_perf
|
157
|
+
|
158
|
+
start_perf 'bound.hash'
|
159
|
+
bench ' bound w/ hash' do
|
160
|
+
provider_hashes.each do |provider|
|
161
|
+
result = TestBoundary.new(provider, overwrite)
|
162
|
+
assert_correctness result
|
163
|
+
end
|
164
|
+
end
|
165
|
+
finish_perf
|
166
|
+
|
167
|
+
Bound.disable_validation
|
168
|
+
|
169
|
+
start_perf 'bound.noval.objt'
|
170
|
+
bench 'bound noval w/ objt' do
|
171
|
+
provider_objects.each do |provider|
|
172
|
+
result = TestBoundary.new(provider, overwrite)
|
173
|
+
assert_correctness result
|
174
|
+
end
|
175
|
+
end
|
176
|
+
finish_perf
|
177
|
+
|
178
|
+
start_perf 'bound.noval.hash'
|
179
|
+
bench 'bound noval w/ hash' do
|
180
|
+
provider_hashes.each do |provider|
|
181
|
+
result = TestBoundary.new(provider, overwrite)
|
182
|
+
assert_correctness result
|
183
|
+
end
|
184
|
+
end
|
185
|
+
finish_perf
|
186
|
+
|
187
|
+
bench 'staticbound w/ objt' do
|
188
|
+
provider_objects.each do |provider|
|
189
|
+
result = StaticBoundary.new(provider, overwrite)
|
190
|
+
assert_correctness result
|
191
|
+
end
|
26
192
|
end
|
27
193
|
|
28
|
-
bench '
|
29
|
-
|
30
|
-
|
194
|
+
bench 'staticbound w/ hash' do
|
195
|
+
provider_hashes.each do |provider|
|
196
|
+
result = StaticBoundary.new(provider, overwrite)
|
197
|
+
assert_correctness result
|
31
198
|
end
|
32
199
|
end
|
33
200
|
|
34
|
-
bench '
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
201
|
+
bench 'structbound w/ objt' do
|
202
|
+
provider_objects.each do |provider|
|
203
|
+
result = StructBoundary.new(
|
204
|
+
overwrite[:foo],
|
205
|
+
[
|
206
|
+
BarStructBoundary.new(provider.bar[0].abc),
|
207
|
+
BarStructBoundary.new(provider.bar[1].abc),
|
208
|
+
],
|
209
|
+
BazStructBoundary.new(provider.baz.gonzo)
|
210
|
+
)
|
211
|
+
assert_correctness result
|
41
212
|
end
|
42
213
|
end
|
43
214
|
|
44
|
-
bench '
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
215
|
+
bench 'structbound w/ hash' do
|
216
|
+
provider_hashes.map do |provider|
|
217
|
+
result = StructBoundary.new(
|
218
|
+
overwrite[:foo],
|
219
|
+
[
|
220
|
+
BarStructBoundary.new(provider[:bar][0][:abc]),
|
221
|
+
BarStructBoundary.new(provider[:bar][1][:abc]),
|
222
|
+
],
|
223
|
+
BazStructBoundary.new(provider[:baz][:gonzo])
|
224
|
+
)
|
225
|
+
assert_correctness result
|
51
226
|
end
|
52
227
|
end
|
data/bound.gemspec
CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.add_development_dependency "bundler", "~> 1.3"
|
21
21
|
spec.add_development_dependency "rake"
|
22
22
|
spec.add_development_dependency "minitest", "~> 5.0.7"
|
23
|
+
spec.add_development_dependency "perftools.rb"
|
23
24
|
|
24
25
|
spec.add_development_dependency "simplecov"
|
25
26
|
end
|
data/lib/bound.rb
CHANGED
@@ -14,292 +14,300 @@ class Bound
|
|
14
14
|
new_bound_class.required(*args)
|
15
15
|
end
|
16
16
|
|
17
|
+
def self.validate?
|
18
|
+
!@validation_disabled
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.disable_validation
|
22
|
+
@validation_disabled = true
|
23
|
+
StaticBoundClass.define_initializer_without_validation
|
24
|
+
end
|
25
|
+
|
17
26
|
private
|
18
27
|
|
19
28
|
def self.new_bound_class
|
20
|
-
Class.new(
|
21
|
-
initialize_values
|
29
|
+
Class.new(StaticBoundClass) do
|
22
30
|
end
|
23
31
|
end
|
24
32
|
|
25
|
-
class
|
26
|
-
|
27
|
-
attr_reader :name, :value
|
28
|
-
attr_accessor :nested_class
|
29
|
-
|
30
|
-
def initialize(name)
|
31
|
-
@name = name
|
32
|
-
@assigned = false
|
33
|
-
end
|
34
|
-
|
35
|
-
def assign(value)
|
36
|
-
@assigned = true
|
37
|
-
if nested_class
|
38
|
-
@value = assign_nested(value)
|
39
|
-
else
|
40
|
-
@value = value
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def assign_nested(value)
|
45
|
-
nested_attribute = NestedAttribute.new(nested_class)
|
46
|
-
nested_attribute.resolve(value)
|
47
|
-
end
|
48
|
-
|
49
|
-
def call_on(object)
|
50
|
-
Caller.call(object, @name)
|
51
|
-
end
|
33
|
+
class BoundValidator
|
34
|
+
attr_accessor :attributes, :optional_attributes, :nested_array_attributes
|
52
35
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
def required?
|
58
|
-
false
|
59
|
-
end
|
36
|
+
def initialize(target, overwrite)
|
37
|
+
@target = target
|
38
|
+
@overwrite = overwrite
|
39
|
+
end
|
60
40
|
|
61
|
-
|
62
|
-
|
41
|
+
def validate!
|
42
|
+
ensure_all_attributes_are_known!
|
43
|
+
attributes.each do |attribute|
|
44
|
+
ensure_present! attribute
|
63
45
|
end
|
64
|
-
|
65
|
-
|
66
|
-
@value.inspect
|
46
|
+
nested_array_attributes.each do |nested_array_attribute|
|
47
|
+
ensure_array! nested_array_attribute
|
67
48
|
end
|
68
49
|
end
|
69
50
|
|
70
|
-
|
71
|
-
def required?; true; end
|
72
|
-
end
|
51
|
+
private
|
73
52
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@assigner = ValueAssigner.new(bound_definition)
|
53
|
+
def ensure_all_attributes_are_known!
|
54
|
+
(overwritten_attrs + target_attrs).each do |attr|
|
55
|
+
unless (attributes + optional_attributes).include? attr
|
56
|
+
message = "Unknown attribute: #{attr}"
|
57
|
+
raise ArgumentError, message
|
80
58
|
end
|
81
59
|
end
|
60
|
+
end
|
82
61
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
class ArrayAssigner
|
88
|
-
def initialize(definitions)
|
89
|
-
@bound_class = definitions.first
|
90
|
-
end
|
91
|
-
|
92
|
-
def resolve(arguments_list)
|
93
|
-
raise ArgumentError.new("Expected #{arguments_list.inspect} to be an array") unless arguments_list.kind_of? Array
|
94
|
-
arguments_list.map do |arguments|
|
95
|
-
@bound_class.new(arguments)
|
96
|
-
end
|
97
|
-
end
|
62
|
+
def ensure_present!(attribute)
|
63
|
+
if !overwritten?(attribute) && !target_has?(attribute)
|
64
|
+
message = "Missing attribute: #{attribute}"
|
65
|
+
raise ArgumentError, message
|
98
66
|
end
|
67
|
+
end
|
99
68
|
|
100
|
-
|
101
|
-
|
102
|
-
|
69
|
+
def ensure_array!(attribute)
|
70
|
+
message = "Expected %s to be an array"
|
71
|
+
if overwritten?(attribute)
|
72
|
+
unless val = overwritten(attribute).kind_of?(Array)
|
73
|
+
raise(ArgumentError, message % val.inspect)
|
103
74
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
75
|
+
elsif target_has?(attribute)
|
76
|
+
unless val = target(attribute).kind_of?(Array)
|
77
|
+
raise(ArgumentError, message % val.inspect)
|
107
78
|
end
|
79
|
+
else
|
108
80
|
end
|
109
81
|
end
|
110
82
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
self.attrs = {}
|
117
|
-
self.nested_attr_classes = {}
|
83
|
+
def overwritten_attrs
|
84
|
+
if @overwrite
|
85
|
+
@overwrite.keys
|
86
|
+
else
|
87
|
+
[]
|
118
88
|
end
|
89
|
+
end
|
119
90
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
end
|
126
|
-
|
127
|
-
set_attributes :optional, attributes, nested_attributes
|
128
|
-
|
129
|
-
self
|
91
|
+
def target_attrs
|
92
|
+
if @target && @target.kind_of?(Hash)
|
93
|
+
@target.keys
|
94
|
+
else
|
95
|
+
[]
|
130
96
|
end
|
97
|
+
end
|
131
98
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
else
|
136
|
-
nested_attributes = {}
|
137
|
-
end
|
99
|
+
def overwritten?(attr)
|
100
|
+
@overwrite && @overwrite.key?(attr)
|
101
|
+
end
|
138
102
|
|
139
|
-
|
103
|
+
def overwritten(attr)
|
104
|
+
@overwrite && @overwrite[attr]
|
105
|
+
end
|
140
106
|
|
141
|
-
|
142
|
-
|
107
|
+
def target_has?(attr)
|
108
|
+
@target &&
|
109
|
+
@target.kind_of?(Hash)?@target.key?(attr):@target.respond_to?(attr)
|
110
|
+
end
|
143
111
|
|
144
|
-
|
112
|
+
def target(attr)
|
113
|
+
@target &&
|
114
|
+
@target.kind_of?(Hash)?@target[attr]:@target.send(attr)
|
115
|
+
end
|
116
|
+
end
|
145
117
|
|
146
|
-
|
147
|
-
|
118
|
+
class StaticBoundClass
|
119
|
+
def ==(other)
|
120
|
+
false unless other
|
121
|
+
true
|
122
|
+
end
|
148
123
|
|
149
|
-
|
150
|
-
|
151
|
-
end
|
124
|
+
def validate!
|
125
|
+
end
|
152
126
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
RequiredAttribute
|
157
|
-
end
|
127
|
+
def self.define_initializer_without_validation
|
128
|
+
define_initializer(nil)
|
129
|
+
end
|
158
130
|
|
159
|
-
|
160
|
-
|
131
|
+
def self.define_initializer(after_init = 'validate!')
|
132
|
+
code = <<-EOR
|
133
|
+
def initialize(target = nil, overwrite = nil)
|
134
|
+
@t, @o = target, overwrite
|
135
|
+
%s
|
161
136
|
end
|
137
|
+
EOR
|
138
|
+
if after_init
|
139
|
+
code = code % " #{after_init}"
|
140
|
+
else
|
141
|
+
code = code % ''
|
142
|
+
end
|
162
143
|
|
163
|
-
|
144
|
+
class_eval code
|
145
|
+
end
|
164
146
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
147
|
+
def self.define_attributes(*attributes)
|
148
|
+
if attributes.last.kind_of? Hash
|
149
|
+
nested_attributes = attributes.pop
|
150
|
+
else
|
151
|
+
nested_attributes = {}
|
171
152
|
end
|
172
153
|
|
173
|
-
|
174
|
-
|
175
|
-
|
154
|
+
if nested_attributes.keys.any? { |a| !a.kind_of? Symbol }
|
155
|
+
message = "Invalid list of attributes: #{nested_attributes.inspect}"
|
156
|
+
raise ArgumentError, message
|
176
157
|
end
|
177
158
|
|
178
|
-
|
179
|
-
attributes.
|
180
|
-
|
181
|
-
get_attribute(attribute).value
|
182
|
-
end
|
183
|
-
end
|
159
|
+
if attributes.any? { |a| !a.kind_of? Symbol }
|
160
|
+
message = "Invalid list of attributes: #{attributes.inspect}"
|
161
|
+
raise ArgumentError, message
|
184
162
|
end
|
185
163
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
get_attribute(attribute).assign value
|
190
|
-
end
|
191
|
-
end
|
164
|
+
nested_attributes.each do |attribute, nested_class|
|
165
|
+
define_nested_delegate attribute, nested_class
|
166
|
+
define_equality attribute
|
192
167
|
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def initialize(seed = nil, overwrite = nil)
|
196
|
-
@attributes = {}
|
197
|
-
raise('Overwrite with object') if overwrite && !overwrite.kind_of?(Hash)
|
198
|
-
seed_with overwrite if overwrite
|
199
|
-
seed_with seed if seed
|
200
|
-
validate!
|
201
|
-
end
|
202
|
-
|
203
|
-
def method_missing(meth, *args, &blk)
|
204
|
-
attribute = meth.to_s.gsub(/=$/, '')
|
205
|
-
raise ArgumentError.new("Unknown attribute: #{self.class}##{attribute}")
|
206
|
-
end
|
207
168
|
|
208
|
-
|
209
|
-
|
210
|
-
|
169
|
+
attributes.each do |attribute|
|
170
|
+
define_delegate attribute
|
171
|
+
define_equality attribute
|
211
172
|
end
|
212
173
|
end
|
213
174
|
|
214
|
-
def
|
215
|
-
|
175
|
+
def self.define_validator
|
176
|
+
attributes = (@attributes || []).map do |attr|
|
177
|
+
":#{attr.to_s}"
|
178
|
+
end.join(',')
|
179
|
+
optional_attributes = (@optional_attributes || []).map do |attr|
|
180
|
+
":#{attr.to_s}"
|
181
|
+
end.join(',')
|
182
|
+
nested_array_attributes = (@nested_array_attributes || []).map do |attr|
|
183
|
+
":#{attr.to_s}"
|
184
|
+
end.join(',')
|
185
|
+
code = <<-EOR
|
186
|
+
def validate!
|
187
|
+
v = Bound::BoundValidator.new(@t, @o)
|
188
|
+
v.attributes = [#{attributes}]
|
189
|
+
v.optional_attributes = [#{optional_attributes}]
|
190
|
+
v.nested_array_attributes = [#{nested_array_attributes}]
|
191
|
+
v.validate!
|
192
|
+
end
|
193
|
+
private :validate!
|
194
|
+
EOR
|
195
|
+
class_eval code
|
216
196
|
end
|
217
197
|
|
218
|
-
def
|
219
|
-
|
220
|
-
|
198
|
+
def self.set_required_attributes(attributes, nested_array_attributes)
|
199
|
+
@attributes ||= []
|
200
|
+
@attributes += attributes
|
201
|
+
@attributes += nested_array_attributes
|
202
|
+
@nested_array_attributes ||= []
|
203
|
+
@nested_array_attributes += nested_array_attributes
|
204
|
+
define_validator
|
221
205
|
end
|
222
206
|
|
223
|
-
def
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
attribute = attribute_class.new(attribute_name)
|
232
|
-
attribute.nested_class = nested_class
|
233
|
-
|
234
|
-
@attributes[attribute_name] = attribute
|
207
|
+
def self.set_optional_attributes(attributes, nested_array_attributes)
|
208
|
+
@optional_attributes ||= []
|
209
|
+
@optional_attributes += attributes
|
210
|
+
@optional_attributes += nested_array_attributes
|
211
|
+
@nested_array_attributes ||= []
|
212
|
+
@nested_array_attributes += nested_array_attributes
|
213
|
+
define_validator
|
235
214
|
end
|
236
215
|
|
237
|
-
def
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
216
|
+
def self.define_equality(attr)
|
217
|
+
@equality ||= []
|
218
|
+
@equality << attr
|
219
|
+
code = <<-EOR
|
220
|
+
def==(other)
|
221
|
+
return false unless other
|
222
|
+
%w{#{@equality.join(' ')}}.all? do |attr|
|
223
|
+
other.respond_to?(attr) &&
|
224
|
+
other.send(attr) == send(attr)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
EOR
|
228
|
+
class_eval code
|
243
229
|
end
|
244
230
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
231
|
+
def self.define_delegate(attr, prefix = '')
|
232
|
+
code = <<-EOR
|
233
|
+
def #{prefix}#{attr}
|
234
|
+
return @o[:#{attr}] if @o && @o.key?(:#{attr})
|
235
|
+
return @t.kind_of?(Hash)? @t[:#{attr}] : @t.#{attr} if @t
|
236
|
+
nil
|
237
|
+
end
|
238
|
+
EOR
|
239
|
+
class_eval code
|
251
240
|
end
|
252
241
|
|
253
|
-
def
|
254
|
-
|
255
|
-
|
256
|
-
|
242
|
+
def self.define_nested_delegate(attr, nested_class)
|
243
|
+
define_delegate attr, 'get_'
|
244
|
+
code = <<-EOR
|
245
|
+
class << self
|
246
|
+
def get_#{attr}_class
|
247
|
+
@#{attr}_class
|
248
|
+
end
|
249
|
+
def set_#{attr}_class(arg)
|
250
|
+
@#{attr}_class = arg
|
251
|
+
end
|
252
|
+
private :set_#{attr}_class
|
253
|
+
end
|
254
|
+
EOR
|
255
|
+
|
256
|
+
if nested_class.kind_of? Array
|
257
|
+
nested_class = nested_class.first
|
258
|
+
code += <<-EOR
|
259
|
+
def #{attr}
|
260
|
+
return @#{attr} if defined? @#{attr}
|
261
|
+
return [] unless val = get_#{attr}
|
262
|
+
@#{attr} ||= val.map{|t| self.class.get_#{attr}_class.new t}
|
263
|
+
end
|
264
|
+
private :get_#{attr}
|
265
|
+
EOR
|
257
266
|
else
|
258
|
-
|
267
|
+
code += <<-EOR
|
268
|
+
def #{attr}
|
269
|
+
return @#{attr} if defined? @#{attr}
|
270
|
+
return nil unless val = get_#{attr}
|
271
|
+
@#{attr} ||= self.class.get_#{attr}_class.new(val)
|
272
|
+
end
|
273
|
+
private :get_#{attr}
|
274
|
+
EOR
|
259
275
|
end
|
260
|
-
|
261
|
-
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
class HashSeeder
|
266
|
-
def initialize(receiver)
|
267
|
-
@receiver = receiver
|
276
|
+
class_eval code
|
277
|
+
self.send :"set_#{attr}_class", nested_class
|
268
278
|
end
|
269
279
|
|
270
|
-
def
|
271
|
-
|
272
|
-
attribute = @receiver.get_attribute(key)
|
273
|
-
next if attribute && attribute.is_assigned?
|
280
|
+
def self.required(*attributes)
|
281
|
+
self.define_attributes(*attributes)
|
274
282
|
|
275
|
-
|
276
|
-
|
283
|
+
array_attributes = []
|
284
|
+
if attributes.last.kind_of? Hash
|
285
|
+
attributes.pop.each do |attr, nested_class|
|
286
|
+
array_attributes << attr if nested_class.kind_of? Array
|
287
|
+
attributes << attr
|
288
|
+
end
|
277
289
|
end
|
278
|
-
end
|
279
|
-
end
|
280
290
|
|
281
|
-
|
282
|
-
|
283
|
-
@receiver = receiver
|
291
|
+
self.set_required_attributes(attributes, array_attributes)
|
292
|
+
self
|
284
293
|
end
|
285
294
|
|
286
|
-
def
|
287
|
-
|
288
|
-
next if attribute.is_assigned?
|
295
|
+
def self.optional(*attributes)
|
296
|
+
self.define_attributes(*attributes)
|
289
297
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
298
|
+
array_attributes = []
|
299
|
+
if attributes.last.kind_of? Hash
|
300
|
+
attributes.pop.each do |attr, nested_class|
|
301
|
+
array_attributes << attr if nested_class.kind_of? Array
|
302
|
+
attributes << attr
|
294
303
|
end
|
295
304
|
end
|
296
|
-
end
|
297
305
|
|
298
|
-
|
299
|
-
|
300
|
-
method = "#{attribute.name}="
|
301
|
-
@receiver.send(method, value)
|
306
|
+
self.set_optional_attributes(attributes, array_attributes)
|
307
|
+
self
|
302
308
|
end
|
303
309
|
|
310
|
+
define_initializer
|
304
311
|
end
|
312
|
+
|
305
313
|
end
|
data/lib/bound/version.rb
CHANGED
data/spec/bound_spec.rb
CHANGED
@@ -15,15 +15,6 @@ describe Bound do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
it 'checks if attribute exists' do
|
19
|
-
[hash, object].each do |subject|
|
20
|
-
user = User.new(subject)
|
21
|
-
|
22
|
-
assert user.has_attribute?(:name)
|
23
|
-
assert user.has_attribute?(:age)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
18
|
it 'fails if attribute is missing' do
|
28
19
|
hash.delete :age
|
29
20
|
|
@@ -55,14 +46,6 @@ describe Bound do
|
|
55
46
|
assert_match(/unknown.+gender/i, exception.message)
|
56
47
|
end
|
57
48
|
|
58
|
-
it 'exposes an attributes method' do
|
59
|
-
user = User.new(hash)
|
60
|
-
|
61
|
-
assert_equal 2, user.get_attributes.size
|
62
|
-
assert_includes user.get_attributes.map(&:name), :name
|
63
|
-
assert_includes user.get_attributes.map(&:name), :age
|
64
|
-
end
|
65
|
-
|
66
49
|
describe 'equality' do
|
67
50
|
let(:user) { User.new(hash) }
|
68
51
|
|
@@ -145,14 +128,6 @@ describe Bound do
|
|
145
128
|
UserWithoutAge.new(subject)
|
146
129
|
end
|
147
130
|
end
|
148
|
-
|
149
|
-
it 'are also included in attributes' do
|
150
|
-
user = UserWithoutAge.new(hash)
|
151
|
-
|
152
|
-
assert_equal 2, user.get_attributes.size
|
153
|
-
assert_includes user.get_attributes.map(&:name), :name
|
154
|
-
assert_includes user.get_attributes.map(&:name), :age
|
155
|
-
end
|
156
131
|
end
|
157
132
|
|
158
133
|
describe 'optional nested attributes' do
|
@@ -183,14 +158,6 @@ describe Bound do
|
|
183
158
|
UserWithProfile.new(subject)
|
184
159
|
end
|
185
160
|
end
|
186
|
-
|
187
|
-
it 'are also included in attributes' do
|
188
|
-
user = UserWithProfile.new(hash)
|
189
|
-
|
190
|
-
assert_equal 2, user.get_attributes.size
|
191
|
-
assert_includes user.get_attributes.map(&:name), :id
|
192
|
-
assert_includes user.get_attributes.map(&:name), :profile
|
193
|
-
end
|
194
161
|
end
|
195
162
|
|
196
163
|
describe 'no attributes' do
|
@@ -273,14 +240,6 @@ describe Bound do
|
|
273
240
|
end
|
274
241
|
|
275
242
|
end
|
276
|
-
|
277
|
-
it 'are also included in attributes' do
|
278
|
-
user = BloggingUser.new(hash)
|
279
|
-
|
280
|
-
assert_equal 2, user.get_attributes.size
|
281
|
-
assert_includes user.get_attributes.map(&:name), :name
|
282
|
-
assert_includes user.get_attributes.map(&:name), :posts
|
283
|
-
end
|
284
243
|
end
|
285
244
|
|
286
245
|
describe 'allows optional as constructor' do
|
@@ -301,20 +260,6 @@ describe Bound do
|
|
301
260
|
end
|
302
261
|
end
|
303
262
|
|
304
|
-
describe '__attributes__' do
|
305
|
-
DeprecatedUser = Bound.required(:name)
|
306
|
-
|
307
|
-
it 'is deprecated' do
|
308
|
-
user = DeprecatedUser.new(:name => 'foo')
|
309
|
-
|
310
|
-
deprecation_warning, _ = capture_io do
|
311
|
-
user.__attributes__
|
312
|
-
end
|
313
|
-
|
314
|
-
assert_match(/deprecated/, deprecation_warning)
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
263
|
describe 'questionmark suffix' do
|
319
264
|
WonderingUser = Bound.required(:asked?)
|
320
265
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bound
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakob Holderbaum
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-05-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -53,6 +53,20 @@ dependencies:
|
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: 5.0.7
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: perftools.rb
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
71
|
name: simplecov
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
125
|
version: '0'
|
112
126
|
requirements: []
|
113
127
|
rubyforge_project:
|
114
|
-
rubygems_version: 2.
|
128
|
+
rubygems_version: 2.2.2
|
115
129
|
signing_key:
|
116
130
|
specification_version: 4
|
117
131
|
summary: Implements a nice helper for fast boundary definitions
|