attributor 5.1.0 → 5.2.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: fe4a2dca77307723d8c18e4f37ded9cba8e54657
4
- data.tar.gz: 175d9d21dd78aaba4e6c534559290cb79d3bf3a2
3
+ metadata.gz: f5472909b262b07126ca30d760aadb3f5bc0d362
4
+ data.tar.gz: 9e66f91088af8389b60d088dd4d7599acfd74d9a
5
5
  SHA512:
6
- metadata.gz: 89de22ecae27da14b59f57b2ea87d3552c6d1dbdd338e07934bc29031fd4e65b664f4faf04fb50f56b2e3687cde7580ba217562a413856fa8cdb403c9bc4a335
7
- data.tar.gz: d8beb7bba6e226b84d1608d080800e65ce632d3b9af0daeb6fa7415f0477d307d63ac9267b4ffd659fe846b9f1b0b3012d4853d8a1bb6a3d4bad4ff037aaa10b
6
+ metadata.gz: a7c963afb8460167b7714ae4dd4ae74f273ef703b11059e354f33f2356adfba3e9433dad3bc4497612beacfcaf67c727249a6e0143b5e45a0219b95e9c61ace0
7
+ data.tar.gz: ef1e2720295ee7456b08cd2c003e044f686958cb4e6ef3738f617d042c1ed2da0f1180e1cc5e11d0b97e0b74046eeead54f4a5b8f6bd843d4e092ab6662baa2b
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 5.2 (12/09/2017)
6
+
7
+ * Fixed describing `Hash` with no keys defined, to still use a given example (no example outputted before this)
8
+ * Fix bug that would occur when defining an attribute carrying a reference object, for which the reference type didn't have `attributes` (for example a Collection).
9
+ * Allows an attribute to override the reference object through its options of its parent (even when its containing object already has one defined).
10
+ * Built code to generate examples in a smarter way when complex conditional requirements are specified.
11
+
12
+
5
13
  ## 5.1
6
14
 
7
15
  * Added `Polymorphic` type. See [polymorphics.rb](spec/support/polymorphics.rb) for example usage.
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'rspec', '~> 3'
27
27
  spec.add_development_dependency 'rspec-its'
28
28
  spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
29
- spec.add_development_dependency('yard', ['~> 0.8.7'])
29
+ spec.add_development_dependency('yard')
30
30
  spec.add_development_dependency('backports', ['~> 3'])
31
31
  spec.add_development_dependency('yardstick', ['~> 0'])
32
32
  spec.add_development_dependency('bundler', ['>= 0'])
@@ -14,6 +14,7 @@ module Attributor
14
14
  require_relative 'attributor/dsl_compiler'
15
15
  require_relative 'attributor/hash_dsl_compiler'
16
16
  require_relative 'attributor/attribute_resolver'
17
+ require_relative 'attributor/smart_attribute_selector'
17
18
 
18
19
  require_relative 'attributor/example_mixin'
19
20
 
@@ -78,6 +78,8 @@ module Attributor
78
78
  # end
79
79
  # @api semiprivate
80
80
  def define(name, attr_type = nil, **opts, &block)
81
+ example_given = opts.key? :example
82
+
81
83
  # add to existing attribute if present
82
84
  if (existing_attribute = attributes[name])
83
85
  if existing_attribute.attributes
@@ -86,27 +88,33 @@ module Attributor
86
88
  end
87
89
  end
88
90
 
89
- # determine inherited attribute
90
- inherited_attribute = nil
91
- if (reference = options[:reference])
92
- if (inherited_attribute = reference.attributes[name])
91
+ # determine inherited type (giving preference to the direct attribute options)
92
+ inherited_type = opts[:reference]
93
+ unless inherited_type
94
+ reference = options[:reference]
95
+ if reference && reference.respond_to?(:attributes) && reference.attributes.key?(name)
96
+ inherited_attribute = reference.attributes[name]
93
97
  opts = inherited_attribute.options.merge(opts) unless attr_type
94
- opts[:reference] = inherited_attribute.type if block_given?
98
+ inherited_type = inherited_attribute.type
99
+ opts[:reference] = inherited_type if block_given?
95
100
  end
96
101
  end
97
102
 
98
103
  # determine attribute type to use
99
104
  if attr_type.nil?
100
105
  if block_given?
101
- attr_type = if inherited_attribute && inherited_attribute.type < Attributor::Collection
106
+ # Don't inherit explicit examples if we've redefined the structure
107
+ # (but preserve the direct example if given here)
108
+ opts.delete :example unless example_given
109
+ attr_type = if inherited_type && inherited_type < Attributor::Collection
102
110
  # override the reference to be the member_attribute's type for collections
103
- opts[:reference] = inherited_attribute.type.member_attribute.type
111
+ opts[:reference] = inherited_type.member_attribute.type
104
112
  Attributor::Collection.of(Struct)
105
113
  else
106
114
  Attributor::Struct
107
115
  end
108
- elsif inherited_attribute
109
- attr_type = inherited_attribute.type
116
+ elsif inherited_type
117
+ attr_type = inherited_type
110
118
  else
111
119
  raise AttributorException, "type for attribute with name: #{name} could not be determined"
112
120
  end
@@ -34,4 +34,9 @@ module Attributor
34
34
  super msg
35
35
  end
36
36
  end
37
+
38
+ # Thrown from SmartAttributeSelector when the requirements of attributes are certainly unfeasible
39
+ class UnfeasibleRequirementsError < AttributorException
40
+ end
41
+
37
42
  end
@@ -0,0 +1,149 @@
1
+ module Attributor
2
+ class SmartAttributeSelector
3
+ attr_accessor :reqs, :accepted, :banned, :remaining
4
+ attr_reader :reqs, :accepted, :banned, :remaining, :keys_with_values
5
+
6
+ def initialize( reqs , attributes, values)
7
+ @reqs = reqs.dup
8
+ @accepted = []
9
+ @banned = []
10
+ @remaining = attributes.dup
11
+ @keys_with_values = values.each_with_object([]){|(k,v),populated| populated.push(k) unless v == nil}
12
+ end
13
+
14
+ def process
15
+ process_required
16
+ process_exclusive
17
+ process_exactly
18
+ process_at_least
19
+ process_at_most
20
+ # Just add the ones that haven't been explicitly rejected
21
+ self.accepted += self.remaining
22
+ self.remaining = []
23
+ self.accepted.uniq!
24
+ self.accepted
25
+ end
26
+
27
+ def process_required
28
+ self.reqs = reqs.each_with_object([]) do |req, rest|
29
+ if req[:type] == :all
30
+ self.accepted += req[:attributes]
31
+ self.remaining -= req[:attributes]
32
+ else
33
+ rest.push req
34
+ end
35
+ end
36
+ end
37
+
38
+ def process_exclusive
39
+ self.reqs = reqs.each_with_object([]) do |req, rest|
40
+ if req[:type] == :exclusive ||
41
+ (req[:type] == :exactly && req[:count] == 1 ) ||
42
+ (req[:type] == :at_most && req[:count] == 1 )
43
+ process_exclusive_set(Array.new(req[:attributes]))
44
+ else
45
+ rest.push req
46
+ end
47
+ end
48
+ end
49
+
50
+ def process_at_least
51
+ self.reqs = reqs.each_with_object([]) do |req, rest|
52
+ if req[:type] == :at_least
53
+ process_at_least_set(Array.new(req[:attributes]), req[:count])
54
+ else
55
+ rest.push req
56
+ end
57
+ end
58
+ end
59
+
60
+ def process_at_most
61
+ self.reqs = reqs.each_with_object([]) do |req, rest|
62
+ if req[:type] == :at_most && req[:count] > 1 # count=1 is already handled in exclusive
63
+ process_at_most_set(Array.new(req[:attributes]), req[:count])
64
+ else
65
+ rest.push req
66
+ end
67
+ end
68
+ end
69
+
70
+ def process_exactly
71
+ self.reqs = reqs.each_with_object([]) do |req, rest|
72
+ if req[:type] == :exactly && req[:count] > 1 # count=1 is already handled in exclusive
73
+ process_exactly_set(Array.new(req[:attributes]), req[:count])
74
+ else
75
+ rest.push req
76
+ end
77
+ end
78
+ end
79
+
80
+ #################
81
+
82
+ def process_exclusive_set( exclusive_set )
83
+ feasible = exclusive_set - banned # available ones to pick (that are not banned)
84
+ # Try to favor attributes that come in with some values, otherwise get the first feasible one
85
+ preferred = feasible & keys_with_values
86
+ pick = (preferred.size == 0 ? feasible : preferred).first
87
+
88
+ if pick
89
+ self.accepted.push( pick )
90
+ else
91
+ raise UnfeasibleRequirementsError unless exclusive_set.empty?
92
+ end
93
+ self.banned += (feasible - [pick])
94
+ self.remaining -= exclusive_set
95
+ end
96
+
97
+ def process_at_least_set( at_least_set, count)
98
+ feasible = at_least_set - banned # available ones to pick (that are not banned)
99
+ preferred = (feasible & keys_with_values)[0,count]
100
+ # Add more if not enough
101
+ pick = if preferred.size < count
102
+ preferred + (feasible - preferred)[0,count-preferred.size]
103
+ else
104
+ preferred
105
+ end
106
+
107
+ unless pick.size == count
108
+ raise UnfeasibleRequirementsError
109
+ end
110
+ self.accepted += pick
111
+ self.remaining -= pick
112
+ end
113
+
114
+ def process_at_most_set( set, count)
115
+ ceil=(count+1)/2
116
+ feasible = set - banned # available ones to pick (that are not banned)
117
+ preferred = (feasible & keys_with_values)[0,ceil]
118
+
119
+ pick = if preferred.size < ceil
120
+ preferred + (feasible - preferred)[0,(ceil)-preferred.size]
121
+ else
122
+ preferred
123
+ end
124
+
125
+ self.accepted += pick
126
+ self.remaining -= pick
127
+ end
128
+
129
+ def process_exactly_set( set, count)
130
+ feasible = set - banned # available ones to pick (that are not banned)
131
+ preferred = (feasible & keys_with_values)[0,count]
132
+
133
+ pick = if preferred.size < count
134
+ preferred + (feasible - preferred)[0,count-preferred.size]
135
+ else
136
+ preferred
137
+ end
138
+
139
+ unless pick.size == count
140
+ raise UnfeasibleRequirementsError
141
+ end
142
+
143
+ self.accepted += pick
144
+ self.remaining -= pick
145
+ end
146
+
147
+
148
+ end
149
+ end
@@ -59,6 +59,7 @@ module Attributor
59
59
 
60
60
  # generates an example Collection
61
61
  # @return An Array of native type objects conforming to the specified member_type
62
+ # TODO: ALLOW to pass "values" for the members?...as values: {id: 1, ...}
62
63
  def self.example(context = nil, options: {})
63
64
  result = []
64
65
  size = options[:size] || (rand(3) + 1)
@@ -16,7 +16,7 @@ module Attributor
16
16
  end
17
17
 
18
18
  class Hash
19
- MAX_EXAMPLE_DEPTH = 5
19
+ MAX_EXAMPLE_DEPTH = 10
20
20
  CIRCULAR_REFERENCE_MARKER = '...'.freeze
21
21
 
22
22
  include Container
@@ -97,6 +97,13 @@ module Attributor
97
97
  @keys
98
98
  end
99
99
 
100
+ def self.requirements
101
+ if @saved_blocks.any?
102
+ definition
103
+ end
104
+ @requirements
105
+ end
106
+
100
107
  def self.definition
101
108
  opts = {
102
109
  key_type: @key_type,
@@ -168,11 +175,23 @@ module Attributor
168
175
  self
169
176
  end
170
177
 
178
+
179
+
171
180
  def self.example_contents(context, parent, **values)
172
181
  hash = ::Hash.new
173
182
  example_depth = context.size
174
-
175
- keys.each do |sub_attribute_name, sub_attribute|
183
+ # Be smart about what attributes to use for the example: i.e. have into account complex requirements
184
+ # that might have been defined in the hash like at_most(1).of ..., exactly(2).of ...etc.
185
+ # But play it safe and default to the previous behavior in case there is any error processing them
186
+ # ( that is until the SmartAttributeSelector class isn't fully tested and ready for prime time)
187
+ begin
188
+ stack = SmartAttributeSelector.new( requirements.map(&:describe), keys.keys , values)
189
+ selected = stack.process
190
+ rescue => e
191
+ selected = keys.keys
192
+ end
193
+
194
+ keys.select{|n,attr| selected.include? n}.each do |sub_attribute_name, sub_attribute|
176
195
  if sub_attribute.attributes
177
196
  # TODO: add option to raise an exception in this case?
178
197
  next if example_depth > MAX_EXAMPLE_DEPTH
@@ -436,6 +455,7 @@ module Attributor
436
455
  end
437
456
  else
438
457
  hash[:value] = { type: value_type.describe(true) }
458
+ hash[:example] = example if example
439
459
  end
440
460
 
441
461
  hash
@@ -1,3 +1,3 @@
1
1
  module Attributor
2
- VERSION = '5.1.0'.freeze
2
+ VERSION = '5.2.0'.freeze
3
3
  end
@@ -66,6 +66,16 @@ describe Attributor::DSLCompiler do
66
66
  it 'accepts explicit nil type' do
67
67
  dsl_compiler.attribute(attribute_name, nil, attribute_options)
68
68
  end
69
+
70
+ context 'but with the attribute also specifying a reference' do
71
+ let(:attribute_options) { { reference: Attributor::CSV } }
72
+ let(:expected_type) { Attributor::CSV }
73
+ let(:expected_options) { attribute_options }
74
+ it 'attribute reference takes precedence over the compiler one (and merges no options)' do
75
+ expect(attribute_options[:reference]).to_not eq(dsl_compiler_options[:reference])
76
+ dsl_compiler.attribute(attribute_name, attribute_options)
77
+ end
78
+ end
69
79
  end
70
80
 
71
81
  context 'for a referenced Model attribute' do
@@ -0,0 +1,272 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe Attributor::SmartAttributeSelector do
4
+
5
+ let(:type) { Attributor::Hash.construct(block) }
6
+ let(:block) do
7
+ proc do
8
+ key :req1, String
9
+ key :req2, String
10
+ key :exc3, String
11
+ key :exc4, String
12
+ key :least1, String
13
+ key :least2, String
14
+ key :exact1, String
15
+ key :exact2, String
16
+ key :most1, String
17
+ key :most2, String
18
+
19
+ requires.all :req1, :req2
20
+ requires.exclusive :exc3, :exc4
21
+ requires.at_least(2).of :least1, :least2
22
+ requires.exactly(1).of :exc3, :exact1, :exact2
23
+ requires.at_most(1).of :most1, :most2
24
+ requires.at_least(1).of :exc4, :exc3
25
+ end
26
+ end
27
+
28
+ let(:reqs){ type.requirements.map(&:describe) }
29
+ let(:attributes){ [] }
30
+ let(:values){ {} }
31
+ let(:remaining_attrs){ [] }
32
+
33
+ subject(:selector){ Attributor::SmartAttributeSelector.new( reqs, attributes , values) }
34
+ let(:accepted_attrs){ [] }
35
+
36
+ after do
37
+ expect(subject.accepted).to contain_exactly(*accepted_attrs)
38
+ expect(subject.remaining).to contain_exactly(*remaining_attrs)
39
+ end
40
+
41
+ context 'process' do
42
+ let(:accepted_attrs){ [:req1, :req2, :exc3, :least1, :least2, :most1] }
43
+ it 'aggregates results from the different requirements' do
44
+ expect(subject).to receive(:process_required).once.and_call_original
45
+ expect(subject).to receive(:process_exclusive).once.and_call_original
46
+ expect(subject).to receive(:process_at_least).once.and_call_original
47
+ expect(subject).to receive(:process_exactly).once.and_call_original
48
+ expect(subject).to receive(:process_at_most).once.and_call_original
49
+ subject.process
50
+ end
51
+ end
52
+
53
+ context 'process_required' do
54
+ context 'processing only required attrs' do
55
+ let(:block) do
56
+ proc do
57
+ key :req1, String
58
+ key :req2, String
59
+ requires.all :req1, :req2
60
+ end
61
+ end
62
+ let(:accepted_attrs){ [:req1,:req2] }
63
+ it 'processes required' do
64
+ subject.process_required
65
+ end
66
+ end
67
+
68
+ context 'processing some required attrs, others attrs without reqs' do
69
+ let(:block) do
70
+ proc do
71
+ key :req1, String
72
+ key :req2, String
73
+ requires.all :req1
74
+ end
75
+ end
76
+ let(:accepted_attrs){ [:req1] }
77
+
78
+ it 'processes required' do
79
+ subject.process_required
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ context 'process_exclusive' do
86
+ context 'uses exclusive,at_most(1) and exactly(1)' do
87
+ let(:block) do
88
+ proc do
89
+ key :req1, String
90
+ key :req2, String
91
+ key :req3, String
92
+ key :req4, String
93
+ key :req5, String
94
+ key :req6, String
95
+
96
+ requires.exclusive :req1, :req2
97
+ requires.at_most(1).of :req3, :req4
98
+ requires.exactly(1).of :req5, :req6
99
+ end
100
+ end
101
+ let(:accepted_attrs){ [:req1,:req3,:req5] }
102
+ it 'processes required' do
103
+ expect(subject).to receive(:process_exclusive_set).with([:req1, :req2]).and_call_original
104
+ expect(subject).to receive(:process_exclusive_set).with([:req3, :req4]).and_call_original
105
+ expect(subject).to receive(:process_exclusive_set).with([:req5, :req6]).and_call_original
106
+
107
+ subject.process_exclusive
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+
114
+ context 'internal functions' do
115
+
116
+ context 'process_exclusive_set' do
117
+ context 'picks the first of the set bans the rest' do
118
+ let(:set){ [:req1, :req2] }
119
+ let(:accepted_attrs){ [:req1] }
120
+
121
+ it 'processes required' do
122
+ subject.process_exclusive_set( set )
123
+ expect(subject.banned).to eq([:req2])
124
+ end
125
+ end
126
+
127
+ context 'picks the first not banned, and bans the rest' do
128
+ let(:set){ [:req2, :req3] }
129
+ let(:accepted_attrs){ [:req3] }
130
+
131
+ it 'processes required' do
132
+ subject.banned = [:req2] # Explicitly ban one
133
+ subject.process_exclusive_set( set )
134
+ expect(subject.banned).to eq([:req2])
135
+ end
136
+ end
137
+
138
+ context 'finds it unfeasible' do
139
+ let(:set){ [:req2, :req3] }
140
+
141
+ it 'processes required' do
142
+ subject.banned = [:req2, :req3] # Ban them all
143
+ expect{
144
+ subject.process_exclusive_set( set )
145
+ }.to raise_error(Attributor::UnfeasibleRequirementsError)
146
+ end
147
+ it 'unless the set was empty to begin with' do
148
+ expect{
149
+ subject.process_exclusive_set( [] )
150
+ }.to_not raise_error
151
+ end
152
+ end
153
+
154
+ context 'favors attributes with values' do
155
+ let(:values){ {req2: 'foo'} }
156
+ let(:set){ [:req1, :req2] }
157
+ let(:accepted_attrs){ [:req2] }
158
+
159
+ it 'processes required' do
160
+ subject.process_exclusive_set( set )
161
+ expect(subject.banned).to eq([:req1])
162
+ end
163
+ end
164
+
165
+ context 'manages the remaining set' do
166
+ let(:attributes){ [:req1, :req2, :req3] }
167
+ let(:set){ [:req1, :req2] }
168
+ let(:accepted_attrs){ [:req1] }
169
+ let(:remaining_attrs){ [:req3] }
170
+
171
+ it 'processes required' do
172
+ subject.process_exclusive_set( set )
173
+ expect(subject.banned).to eq([:req2])
174
+ end
175
+ end
176
+ end
177
+
178
+ context 'process_at_least_set' do
179
+ context 'picks the count in order' do
180
+ let(:set){ [:req1, :req2, :req3] }
181
+ let(:accepted_attrs){ [:req1,:req2] }
182
+
183
+ it 'processes required' do
184
+ subject.process_at_least_set( set,2 )
185
+ end
186
+ end
187
+
188
+ context 'picks the count in order, skipping banned' do
189
+ let(:set){ [:req1, :req2, :req3] }
190
+ let(:accepted_attrs){ [:req1,:req3] }
191
+
192
+ it 'processes required' do
193
+ subject.banned = [:req2] # Explicitly ban one
194
+ subject.process_at_least_set( set,2 )
195
+ end
196
+ end
197
+
198
+ context 'finds it unfeasible' do
199
+ let(:set){ [:req1, :req2, :req3] }
200
+
201
+ it 'processes required' do
202
+ expect{
203
+ subject.process_at_least_set( set,4 )
204
+ }.to raise_error(Attributor::UnfeasibleRequirementsError)
205
+ end
206
+ end
207
+
208
+ context 'favors attributes with values' do
209
+ let(:values){ {req1: 'foo', req3: 'bar'} }
210
+ let(:set){ [:req1, :req2, :req3] }
211
+ let(:accepted_attrs){ [:req1,:req3] }
212
+
213
+ it 'processes required' do
214
+ subject.process_at_least_set( set,2 )
215
+ end
216
+ end
217
+ end
218
+
219
+ context 'process_at_most_set' do
220
+ context 'picks half the max count in attr order' do
221
+ let(:set){ [:req1, :req2, :req3, :req4, :req5] }
222
+ let(:accepted_attrs){ [:req1,:req2] }
223
+
224
+ it 'processes required' do
225
+ subject.process_at_most_set( set,4 )
226
+ end
227
+ end
228
+ context 'favors attributes with values (and refills with others)' do
229
+ let(:values){ {req3: 'foo', req5: 'bar'} }
230
+ let(:set){ [:req1, :req2, :req3, :req4, :req5] }
231
+ let(:accepted_attrs){ [:req3,:req5,:req1] }
232
+
233
+ it 'processes required' do
234
+ subject.process_at_most_set( set,5 )
235
+ end
236
+ end
237
+ end
238
+
239
+ context 'process_exactly_set' do
240
+ context 'picks exact count in attr order' do
241
+ let(:set){ [:req1, :req2, :req3, :req4, :req5] }
242
+ let(:accepted_attrs){ [:req1,:req2] }
243
+
244
+ it 'processes required' do
245
+ subject.process_exactly_set( set, 2 )
246
+ end
247
+ end
248
+ context 'favors attributes with values (and refills with others)' do
249
+ let(:values){ {req3: 'foo'} }
250
+ let(:set){ [:req1, :req2, :req3, :req4, :req5] }
251
+ let(:accepted_attrs){ [:req3,:req1] }
252
+
253
+ it 'processes required' do
254
+ subject.process_exactly_set( set, 2 )
255
+ end
256
+ end
257
+
258
+ context 'finds it unfeasible' do
259
+ let(:set){ [:req1, :req2] }
260
+
261
+ it 'processes required' do
262
+ subject.banned = [:req2] # Explicitly ban one
263
+ expect{
264
+ subject.process_exactly_set( set, 2 )
265
+ }.to raise_error(Attributor::UnfeasibleRequirementsError)
266
+ end
267
+ end
268
+
269
+ end
270
+
271
+ end
272
+ end
@@ -497,6 +497,22 @@ describe Attributor::Hash do
497
497
  end
498
498
  end
499
499
 
500
+ context '.requirements' do
501
+ let(:type) { Attributor::Hash.construct(block) }
502
+
503
+ context 'forces processing of lazy key initialization' do
504
+ let(:block) do
505
+ proc do
506
+ key 'name', String
507
+ requires 'name'
508
+ end
509
+ end
510
+
511
+ it 'lists the requirements' do
512
+ expect(type.requirements).to_not be_empty
513
+ end
514
+ end
515
+ end
500
516
  context '#validate' do
501
517
  context 'for a key and value typed hash' do
502
518
  let(:key_type) { Integer }
@@ -651,7 +667,7 @@ describe Attributor::Hash do
651
667
  at_least(1).of 'consistency', 'availability', 'partitioning'
652
668
  end
653
669
  # Silly example, just to show that block and inline requires can be combined
654
- requires.at_most(3).of 'consistency', 'availability', 'partitioning'
670
+ requires.at_most(1).of 'consistency', 'availability', 'partitioning'
655
671
  end
656
672
  end
657
673
 
@@ -663,6 +679,37 @@ describe Attributor::Hash do
663
679
  )
664
680
  end
665
681
  end
682
+ context 'using a combo of things to test example gen' do
683
+ let(:block) do
684
+ proc do
685
+ key :req1, String
686
+ key :req2, String
687
+ key :exc3, String
688
+ key :exc4, String
689
+ key :least1, String
690
+ key :least2, String
691
+ key :exact1, String
692
+ key :exact2, String
693
+ key :most1, String
694
+ key :most2, String
695
+
696
+ requires.all :req1, :req2
697
+ requires.exclusive :exc3, :exc4
698
+ requires.at_least(2).of :least1, :least2
699
+ requires.exactly(1).of :exc3, :exact1, :exact2
700
+ requires.at_most(1).of :most1, :most2
701
+ requires.at_least(1).of :exc4, :exc3
702
+ end
703
+ end
704
+ it 'comes up with a reasonably good set' do
705
+ ex = type.example
706
+ expect(ex.keys).to match([:req1, :req2, :exc3, :least1, :least2, :most1])
707
+ end
708
+ it 'it favors picking attributes with data' do
709
+ ex = type.example(nil,{most2: "data"})
710
+ expect(ex.keys).to match([:req1, :req2, :exc3, :least1, :least2, :most2])
711
+ end
712
+ end
666
713
  end
667
714
  end
668
715
 
@@ -687,6 +734,13 @@ describe Attributor::Hash do
687
734
  expect(description[:name]).to eq('Hash')
688
735
  expect(description[:key]).to eq(type: { name: 'Object', id: 'Attributor-Object', family: 'any' })
689
736
  expect(description[:value]).to eq(type: { name: 'Object', id: 'Attributor-Object', family: 'any' })
737
+ expect(description).to_not have_key(:example)
738
+ end
739
+ context 'when there is a given example' do
740
+ let(:example) { { 'one' => 1, two: 2 } }
741
+ it 'uses it, even though there are not individual keys' do
742
+ expect(description[:example]).to eq(example)
743
+ end
690
744
  end
691
745
  end
692
746
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attributor
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-08-18 00:00:00.000000000 Z
12
+ date: 2017-12-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hashie
@@ -99,16 +99,16 @@ dependencies:
99
99
  name: yard
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - "~>"
102
+ - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: 0.8.7
104
+ version: '0'
105
105
  type: :development
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
- - - "~>"
109
+ - - ">="
110
110
  - !ruby/object:Gem::Version
111
- version: 0.8.7
111
+ version: '0'
112
112
  - !ruby/object:Gem::Dependency
113
113
  name: backports
114
114
  requirement: !ruby/object:Gem::Requirement
@@ -338,6 +338,7 @@ files:
338
338
  - lib/attributor/families/numeric.rb
339
339
  - lib/attributor/families/temporal.rb
340
340
  - lib/attributor/hash_dsl_compiler.rb
341
+ - lib/attributor/smart_attribute_selector.rb
341
342
  - lib/attributor/type.rb
342
343
  - lib/attributor/types/bigdecimal.rb
343
344
  - lib/attributor/types/boolean.rb
@@ -371,6 +372,7 @@ files:
371
372
  - spec/extras/field_selector/field_selector_spec.rb
372
373
  - spec/families_spec.rb
373
374
  - spec/hash_dsl_compiler_spec.rb
375
+ - spec/smart_attribute_selector_spec.rb
374
376
  - spec/spec_helper.rb
375
377
  - spec/support/hashes.rb
376
378
  - spec/support/models.rb
@@ -419,7 +421,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
419
421
  version: '0'
420
422
  requirements: []
421
423
  rubyforge_project:
422
- rubygems_version: 2.5.1
424
+ rubygems_version: 2.6.12
423
425
  signing_key:
424
426
  specification_version: 4
425
427
  summary: A powerful attribute and type management library for Ruby
@@ -432,6 +434,7 @@ test_files:
432
434
  - spec/extras/field_selector/field_selector_spec.rb
433
435
  - spec/families_spec.rb
434
436
  - spec/hash_dsl_compiler_spec.rb
437
+ - spec/smart_attribute_selector_spec.rb
435
438
  - spec/spec_helper.rb
436
439
  - spec/support/hashes.rb
437
440
  - spec/support/models.rb
@@ -460,4 +463,3 @@ test_files:
460
463
  - spec/types/time_spec.rb
461
464
  - spec/types/type_spec.rb
462
465
  - spec/types/uri_spec.rb
463
- has_rdoc: