bound 1.1.1 → 2.0.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.
- 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
|