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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 290d76f9ebce3f1b1b815ecbaac8f7b0320e7a7c
4
- data.tar.gz: ea47e2f076c07eeaa1e45e14add1c5fc6683289b
3
+ metadata.gz: cb1d1bdcd67442312ce8e48fa04a1e202aca15a2
4
+ data.tar.gz: 1a506e8c642e84938a4f32ae8f97cf2ed579b05b
5
5
  SHA512:
6
- metadata.gz: d42bb29fd3b7f5285e8278a12a23eb85ed4fd25960445f4d9cebb6f6c66e01e9fd76aa23f229c039004add07d6409e79bc41d21e8734a4454fae78ce7b016d7d
7
- data.tar.gz: 269d4c1f5a82337aca2b520e82d614e50aae40183363c157dc30c06d56cfbb12c6bf980f991d0a09eed5010333221d49f89598c7ff68e289f388fdcc674857b7
6
+ metadata.gz: fa1024912a23354e5bb1ec42ee3518b6b2de5e5e8229eb2182c93f8b69783dc5dfc16e03d0f5da3d6f525ed4472b028f5787952848cc04795728786223df3cf3
7
+ data.tar.gz: a27f0003a78e6ad4fbc69362cb6da84707c6b5f5887e900cec0eccf0f23879b1bdf6cc393e4b7313c4e7644827e1faee1c42745bcbc0eb455e89f58c1534df5a
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ prof__*
@@ -2,8 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 2.0.0
4
4
  - 1.9.3
5
- - jruby-18mode # JRuby in 1.8 mode
6
- - jruby-19mode # JRuby in 1.9 mode
7
- - rbx
8
5
  - 1.8.7
9
6
  - ree
7
+ - jruby-18mode # JRuby in 1.8 mode
8
+ - jruby-19mode # JRuby in 1.9 mode
data/Rakefile CHANGED
@@ -8,6 +8,5 @@ task :default => :spec
8
8
  Rake::TestTask.new(:spec) do |test|
9
9
  test.test_files = FileList["spec/**/*_spec.rb"]
10
10
  test.libs << "spec"
11
- test.warning = true
12
11
  test.verbose = true
13
12
  end
@@ -2,10 +2,102 @@ $: << 'lib'
2
2
  require 'bound'
3
3
  require 'benchmark'
4
4
 
5
- TestBoundary = Bound.required(:abc, :def, :ged)
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
- ManualBoundary = Provider = Class.new do
8
- attr_accessor :abc, :def, :ged
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
- providers = 10_000.times.map do |i|
21
- provider = Provider.new
22
- provider.abc = "abc#{i}"
23
- provider.def = "def#{i}"
24
- provider.ged = "ged#{i}"
25
- provider
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 'bound w/ objt' do
29
- providers.map do |provider|
30
- TestBoundary.new(provider)
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 'bound w/ hash' do
35
- providers.map do |provider|
36
- TestBoundary.new({
37
- :abc => provider.abc,
38
- :def => provider.def,
39
- :ged => provider.ged
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 'plain' do
45
- providers.map do |provider|
46
- test = ManualBoundary.new
47
- test.abc = provider.abc
48
- test.def = provider.def
49
- test.ged = provider.ged
50
- test
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
@@ -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
@@ -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(BoundClass) do
21
- initialize_values
29
+ Class.new(StaticBoundClass) do
22
30
  end
23
31
  end
24
32
 
25
- class BoundClass
26
- class Attribute
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
- def valid?
54
- !required? || is_assigned?
55
- end
56
-
57
- def required?
58
- false
59
- end
36
+ def initialize(target, overwrite)
37
+ @target = target
38
+ @overwrite = overwrite
39
+ end
60
40
 
61
- def is_assigned?
62
- !!@assigned
41
+ def validate!
42
+ ensure_all_attributes_are_known!
43
+ attributes.each do |attribute|
44
+ ensure_present! attribute
63
45
  end
64
-
65
- def inspect
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
- class RequiredAttribute < Attribute
71
- def required?; true; end
72
- end
51
+ private
73
52
 
74
- class NestedAttribute
75
- def initialize(bound_definition)
76
- if bound_definition.kind_of?(Array)
77
- @assigner = ArrayAssigner.new(bound_definition)
78
- else
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
- def resolve(bound_arguments)
84
- @assigner.resolve(bound_arguments)
85
- end
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
- class ValueAssigner
101
- def initialize(definition)
102
- @bound_class = definition
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
- def resolve(arguments)
106
- @bound_class.new(arguments)
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
- class << self
113
- attr_accessor :attrs, :nested_attr_classes
114
-
115
- def initialize_values
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
- def optional(*attributes)
121
- if attributes.last.kind_of? Hash
122
- nested_attributes = attributes.pop
123
- else
124
- nested_attributes = {}
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
- def required(*attributes)
133
- if attributes.last.kind_of? Hash
134
- nested_attributes = attributes.pop
135
- else
136
- nested_attributes = {}
137
- end
99
+ def overwritten?(attr)
100
+ @overwrite && @overwrite.key?(attr)
101
+ end
138
102
 
139
- set_attributes :required, attributes, nested_attributes
103
+ def overwritten(attr)
104
+ @overwrite && @overwrite[attr]
105
+ end
140
106
 
141
- self
142
- end
107
+ def target_has?(attr)
108
+ @target &&
109
+ @target.kind_of?(Hash)?@target.key?(attr):@target.respond_to?(attr)
110
+ end
143
111
 
144
- private
112
+ def target(attr)
113
+ @target &&
114
+ @target.kind_of?(Hash)?@target[attr]:@target.send(attr)
115
+ end
116
+ end
145
117
 
146
- def set_attributes(flag, attributes, nested_attributes = {})
147
- is_optional = flag == :optional
118
+ class StaticBoundClass
119
+ def ==(other)
120
+ false unless other
121
+ true
122
+ end
148
123
 
149
- if attributes.any? { |a| !a.kind_of? Symbol }
150
- raise ArgumentError.new("Invalid list of attributes: #{attributes.inspect}")
151
- end
124
+ def validate!
125
+ end
152
126
 
153
- attribute_class = if is_optional
154
- Attribute
155
- else
156
- RequiredAttribute
157
- end
127
+ def self.define_initializer_without_validation
128
+ define_initializer(nil)
129
+ end
158
130
 
159
- attributes.each do |attribute|
160
- self.attrs[attribute] = attribute_class
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
- define_attribute_accessors attributes
144
+ class_eval code
145
+ end
164
146
 
165
- if nested_attributes.any?
166
- set_attributes flag, nested_attributes.keys
167
- nested_attributes.each do |attribute_name, nested_class|
168
- self.nested_attr_classes[attribute_name] = nested_class
169
- end
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
- def define_attribute_accessors(attributes)
174
- define_attribute_readers attributes
175
- define_attribute_writers attributes
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
- def define_attribute_readers(attributes)
179
- attributes.each do |attribute|
180
- define_method attribute do
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
- def define_attribute_writers(attributes)
187
- attributes.each do |attribute|
188
- define_method :"#{attribute}=" do |value|
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
- def get_attributes
209
- self.class.attrs.keys.map do |attribute_name|
210
- get_attribute(attribute_name)
169
+ attributes.each do |attribute|
170
+ define_delegate attribute
171
+ define_equality attribute
211
172
  end
212
173
  end
213
174
 
214
- def has_attribute?(attr)
215
- self.class.attrs.keys.include? attr
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 __attributes__
219
- puts "BoundClass#__attributes__ is deprecated: use get_attributes"
220
- get_attributes.map(&:name)
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 get_attribute(attribute_name)
224
- return @attributes[attribute_name] if @attributes.has_key? attribute_name
225
-
226
- attribute_class = self.class.attrs[attribute_name]
227
- return nil if attribute_class.nil?
228
-
229
- nested_class = self.class.nested_attr_classes[attribute_name]
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 ==(other)
238
- return false unless other
239
-
240
- get_attributes.all? do |attribute|
241
- attribute.value == Caller.call(other, attribute.name)
242
- end
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
- private
246
-
247
- def validate!
248
- get_attributes.each do |attribute|
249
- raise ArgumentError.new("Missing attribute: #{self.class}##{attribute.name}") unless attribute.valid?
250
- end
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 seed_with(seed)
254
- case seed
255
- when Hash
256
- seeder = HashSeeder.new(self)
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
- seeder = ObjectSeeder.new(self)
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
- seeder.seed(seed)
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 seed(hash)
271
- hash.each do |key, value|
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
- method = "#{key}="
276
- @receiver.send method, value
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
- class ObjectSeeder
282
- def initialize(receiver)
283
- @receiver = receiver
291
+ self.set_required_attributes(attributes, array_attributes)
292
+ self
284
293
  end
285
294
 
286
- def seed(object)
287
- @receiver.get_attributes.each do |attribute|
288
- next if attribute.is_assigned?
295
+ def self.optional(*attributes)
296
+ self.define_attributes(*attributes)
289
297
 
290
- begin
291
- value = attribute.call_on(object)
292
- assign_to_receiver attribute, value
293
- rescue NoMethodError
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
- private
299
- def assign_to_receiver(attribute, value)
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
@@ -1,3 +1,3 @@
1
1
  class Bound
2
- VERSION = "1.1.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -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: 1.1.1
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: 2016-12-08 00:00:00.000000000 Z
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.5.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