attributor 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: