attributor 5.1.0 → 5.5
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/.travis.yml +4 -3
- data/CHANGELOG.md +145 -135
- data/attributor.gemspec +5 -6
- data/lib/attributor.rb +17 -2
- data/lib/attributor/attribute.rb +39 -9
- data/lib/attributor/dsl_compiler.rb +17 -9
- data/lib/attributor/exceptions.rb +5 -0
- data/lib/attributor/extras/field_selector.rb +4 -0
- data/lib/attributor/families/numeric.rb +19 -6
- data/lib/attributor/families/temporal.rb +16 -9
- data/lib/attributor/hash_dsl_compiler.rb +6 -6
- data/lib/attributor/smart_attribute_selector.rb +149 -0
- data/lib/attributor/type.rb +27 -4
- data/lib/attributor/types/bigdecimal.rb +7 -2
- data/lib/attributor/types/boolean.rb +7 -2
- data/lib/attributor/types/class.rb +2 -2
- data/lib/attributor/types/collection.rb +22 -5
- data/lib/attributor/types/container.rb +3 -3
- data/lib/attributor/types/csv.rb +5 -1
- data/lib/attributor/types/date.rb +9 -3
- data/lib/attributor/types/date_time.rb +8 -2
- data/lib/attributor/types/float.rb +4 -3
- data/lib/attributor/types/hash.rb +105 -21
- data/lib/attributor/types/integer.rb +7 -1
- data/lib/attributor/types/model.rb +2 -2
- data/lib/attributor/types/object.rb +5 -0
- data/lib/attributor/types/polymorphic.rb +3 -2
- data/lib/attributor/types/string.rb +20 -1
- data/lib/attributor/types/struct.rb +1 -1
- data/lib/attributor/types/symbol.rb +5 -0
- data/lib/attributor/types/tempfile.rb +4 -0
- data/lib/attributor/types/time.rb +7 -3
- data/lib/attributor/types/uri.rb +9 -1
- data/lib/attributor/version.rb +1 -1
- data/spec/attribute_spec.rb +42 -7
- data/spec/dsl_compiler_spec.rb +16 -6
- data/spec/extras/field_selector/field_selector_spec.rb +9 -0
- data/spec/hash_dsl_compiler_spec.rb +2 -2
- data/spec/smart_attribute_selector_spec.rb +272 -0
- data/spec/support/integers.rb +7 -0
- data/spec/type_spec.rb +1 -1
- data/spec/types/bigdecimal_spec.rb +8 -0
- data/spec/types/boolean_spec.rb +10 -0
- data/spec/types/class_spec.rb +0 -1
- data/spec/types/collection_spec.rb +16 -0
- data/spec/types/date_spec.rb +9 -0
- data/spec/types/date_time_spec.rb +9 -0
- data/spec/types/float_spec.rb +8 -0
- data/spec/types/hash_spec.rb +181 -9
- data/spec/types/integer_spec.rb +10 -1
- data/spec/types/model_spec.rb +14 -3
- data/spec/types/string_spec.rb +10 -0
- data/spec/types/temporal_spec.rb +5 -1
- data/spec/types/time_spec.rb +9 -0
- data/spec/types/uri_spec.rb +9 -0
- metadata +24 -34
@@ -1,7 +1,9 @@
|
|
1
1
|
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
class Integer
|
4
|
+
class Integer
|
5
|
+
include Attributor::Numeric
|
6
|
+
|
5
7
|
EXAMPLE_RANGE = 1000
|
6
8
|
|
7
9
|
def self.native_type
|
@@ -53,5 +55,9 @@ module Attributor
|
|
53
55
|
end
|
54
56
|
true
|
55
57
|
end
|
58
|
+
|
59
|
+
def self.json_schema_type
|
60
|
+
:integer
|
61
|
+
end
|
56
62
|
end
|
57
63
|
end
|
@@ -105,7 +105,7 @@ module Attributor
|
|
105
105
|
result = new
|
106
106
|
result.extend(ExampleMixin)
|
107
107
|
|
108
|
-
result.lazy_attributes = example_contents(context, result, values)
|
108
|
+
result.lazy_attributes = example_contents(context, result, **values)
|
109
109
|
else
|
110
110
|
result = new
|
111
111
|
end
|
@@ -192,7 +192,7 @@ module Attributor
|
|
192
192
|
next
|
193
193
|
end
|
194
194
|
|
195
|
-
hash[name.to_sym] = attribute.dump(value, context: context + [name])
|
195
|
+
hash[name.to_sym] = attribute.dump(value, context: context + [name], **_opts)
|
196
196
|
end
|
197
197
|
ensure
|
198
198
|
@dumping = false
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'active_support'
|
2
1
|
|
3
2
|
require_relative '../exceptions'
|
4
3
|
|
@@ -95,7 +94,9 @@ module Attributor
|
|
95
94
|
value
|
96
95
|
elsif value.is_a?(::String)
|
97
96
|
decode_json(value, context)
|
98
|
-
elsif value.respond_to?(:
|
97
|
+
elsif value.respond_to?(:to_h)
|
98
|
+
value.to_h
|
99
|
+
elsif value.respond_to?(:to_hash) # Deprecate this in lieu of to_h only?
|
99
100
|
value.to_hash
|
100
101
|
else
|
101
102
|
raise Attributor::IncompatibleTypeError, context: context, value_type: value.class, type: self
|
@@ -8,7 +8,7 @@ module Attributor
|
|
8
8
|
|
9
9
|
def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
10
10
|
if value.is_a?(Enumerable)
|
11
|
-
raise IncompatibleTypeError
|
11
|
+
raise IncompatibleTypeError.new(context: context, value_type: value.class, type: self)
|
12
12
|
end
|
13
13
|
|
14
14
|
value && String(value)
|
@@ -32,5 +32,24 @@ module Attributor
|
|
32
32
|
def self.family
|
33
33
|
'string'
|
34
34
|
end
|
35
|
+
|
36
|
+
def self.json_schema_type
|
37
|
+
:string
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: we're passing the attribute options for now...might need to rethink ...although these are type-specific...
|
41
|
+
# TODO: multipleOf, minimum, maximum, exclusiveMinimum and exclusiveMaximum
|
42
|
+
def self.as_json_schema( shallow: false, example: nil, attribute_options: {} )
|
43
|
+
h = super
|
44
|
+
opts = ( self.respond_to?(:options) ) ? self.options.merge( attribute_options ) : attribute_options
|
45
|
+
h[:pattern] = self.human_readable_regexp(opts[:regexp]) if opts[:regexp]
|
46
|
+
# TODO: minLength, maxLength
|
47
|
+
h
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.human_readable_regexp( reg )
|
51
|
+
return $1 if reg.to_s =~ /\(\?[^:]+:(.+)\)/
|
52
|
+
reg
|
53
|
+
end
|
35
54
|
end
|
36
55
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
class Time
|
5
|
-
include
|
4
|
+
class Time
|
5
|
+
include Temporal
|
6
6
|
|
7
7
|
def self.native_type
|
8
8
|
::Time
|
@@ -29,11 +29,15 @@ module Attributor
|
|
29
29
|
begin
|
30
30
|
return ::Time.parse(value)
|
31
31
|
rescue ArgumentError
|
32
|
-
raise Attributor::DeserializationError
|
32
|
+
raise Attributor::DeserializationError.new(context: context, from: value.class, encoding: 'Time', value: value)
|
33
33
|
end
|
34
34
|
else
|
35
35
|
raise CoercionError, context: context, from: value.class, to: self, value: value
|
36
36
|
end
|
37
37
|
end
|
38
|
+
|
39
|
+
def self.json_schema_string_format
|
40
|
+
:time
|
41
|
+
end
|
38
42
|
end
|
39
43
|
end
|
data/lib/attributor/types/uri.rb
CHANGED
@@ -22,6 +22,14 @@ module Attributor
|
|
22
22
|
::URI::Generic
|
23
23
|
end
|
24
24
|
|
25
|
+
def self.json_schema_type
|
26
|
+
:string
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.json_schema_string_format
|
30
|
+
:uri
|
31
|
+
end
|
32
|
+
|
25
33
|
def self.example(_context = nil, options: {})
|
26
34
|
URI(Randgen.uri)
|
27
35
|
end
|
@@ -34,7 +42,7 @@ module Attributor
|
|
34
42
|
when ::String
|
35
43
|
URI(value)
|
36
44
|
else
|
37
|
-
raise CoercionError
|
45
|
+
raise CoercionError.new(context: context, from: value.class, to: self, value: value)
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
data/lib/attributor/version.rb
CHANGED
data/spec/attribute_spec.rb
CHANGED
@@ -36,17 +36,48 @@ describe Attributor::Attribute do
|
|
36
36
|
it { should eq other_attribute }
|
37
37
|
end
|
38
38
|
|
39
|
+
context 'describe_json_schema' do
|
40
|
+
let(:type) { PositiveIntegerType }
|
41
|
+
|
42
|
+
let(:attribute_options) do
|
43
|
+
{
|
44
|
+
values: [1,20],
|
45
|
+
description: "something",
|
46
|
+
example: 20,
|
47
|
+
max: 1000,
|
48
|
+
default: 1
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'reports all of the possible attributes' do
|
53
|
+
let(:js){ subject.as_json_schema(example: 20) }
|
54
|
+
|
55
|
+
it 'including the attribute-specific ones' do
|
56
|
+
expect(js[:enum]).to eq( [1,20])
|
57
|
+
expect(js[:description]).to eq( "something")
|
58
|
+
expect(js[:default]).to eq(1)
|
59
|
+
expect(js[:example]).to eq(20)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'as well as the type-specific ones' do
|
63
|
+
expect(js[:type]).to eq(:integer)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
39
69
|
context 'describe' do
|
40
|
-
let(:attribute_options) { {
|
70
|
+
let(:attribute_options) { {required: true, values: ['one'], description: "something", min: 0} }
|
41
71
|
let(:expected) do
|
42
|
-
h = {
|
43
|
-
common = attribute_options.select
|
44
|
-
h.merge!(common)
|
45
|
-
h[:options] = {
|
72
|
+
h = {type: {name: 'String', id: type.id, family: type.family}}
|
73
|
+
common = attribute_options.select{|k,v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k }
|
74
|
+
h.merge!( common )
|
75
|
+
h[:options] = {:min => 0 }
|
46
76
|
h
|
47
77
|
end
|
48
78
|
|
49
|
-
|
79
|
+
# It has both the type-included options (min) as well as the attribute options (max)
|
80
|
+
its(:describe) { should == expected }
|
50
81
|
|
51
82
|
context 'with example options' do
|
52
83
|
let(:attribute_options) { { description: 'something', example: 'ex_def' } }
|
@@ -313,7 +344,11 @@ describe Attributor::Attribute do
|
|
313
344
|
let(:value) { '1' }
|
314
345
|
|
315
346
|
it 'delegates to type.load' do
|
316
|
-
|
347
|
+
# Need to add the "anything" of the 3rd element, as in ruby < 2.7 it comes as an empty hash
|
348
|
+
expect(type).to receive(:load) do |v, c, _other|
|
349
|
+
expect(v).to eq(value)
|
350
|
+
expect(c).to eq(context)
|
351
|
+
end
|
317
352
|
attribute.load(value, context)
|
318
353
|
end
|
319
354
|
|
data/spec/dsl_compiler_spec.rb
CHANGED
@@ -4,7 +4,7 @@ describe Attributor::DSLCompiler do
|
|
4
4
|
let(:target) { double('model', attributes: {}) }
|
5
5
|
|
6
6
|
let(:dsl_compiler_options) { {} }
|
7
|
-
subject(:dsl_compiler) { Attributor::DSLCompiler.new(target, dsl_compiler_options) }
|
7
|
+
subject(:dsl_compiler) { Attributor::DSLCompiler.new(target, **dsl_compiler_options) }
|
8
8
|
|
9
9
|
let(:attribute_name) { :name }
|
10
10
|
let(:type) { Attributor::String }
|
@@ -35,7 +35,7 @@ describe Attributor::DSLCompiler do
|
|
35
35
|
|
36
36
|
it 'creates an attribute given a name, type, and options' do
|
37
37
|
expect(Attributor::Attribute).to receive(:new).with(expected_type, expected_options)
|
38
|
-
dsl_compiler.attribute(attribute_name, type, attribute_options)
|
38
|
+
dsl_compiler.attribute(attribute_name, type, **attribute_options)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -60,11 +60,21 @@ describe Attributor::DSLCompiler do
|
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'creates an attribute with the inherited type and merged options' do
|
63
|
-
dsl_compiler.attribute(attribute_name, attribute_options)
|
63
|
+
dsl_compiler.attribute(attribute_name, **attribute_options)
|
64
64
|
end
|
65
65
|
|
66
66
|
it 'accepts explicit nil type' do
|
67
|
-
dsl_compiler.attribute(attribute_name, nil, attribute_options)
|
67
|
+
dsl_compiler.attribute(attribute_name, nil, **attribute_options)
|
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
|
68
78
|
end
|
69
79
|
end
|
70
80
|
|
@@ -103,12 +113,12 @@ describe Attributor::DSLCompiler do
|
|
103
113
|
it 'sets the type of the attribute to Struct' do
|
104
114
|
expect(Attributor::Attribute).to receive(:new)
|
105
115
|
.with(expected_type, description: 'The turkey', reference: Turkey)
|
106
|
-
dsl_compiler.attribute(attribute_name, attribute_options, &attribute_block)
|
116
|
+
dsl_compiler.attribute(attribute_name, **attribute_options, &attribute_block)
|
107
117
|
end
|
108
118
|
|
109
119
|
it 'passes the correct reference to the created attribute' do
|
110
120
|
expect(Attributor::Attribute).to receive(:new).with(expected_type, expected_options)
|
111
|
-
dsl_compiler.attribute(attribute_name, type, attribute_options, &attribute_block)
|
121
|
+
dsl_compiler.attribute(attribute_name, type, **attribute_options, &attribute_block)
|
112
122
|
end
|
113
123
|
end
|
114
124
|
end
|
@@ -33,4 +33,13 @@ describe Attributor::FieldSelector do
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
context '.as_json_schema' do
|
38
|
+
subject(:js){ type.as_json_schema }
|
39
|
+
it 'adds the right attributes' do
|
40
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
41
|
+
expect(js[:type]).to eq(:string)
|
42
|
+
expect(js[:'x-type_name']).to eq('FieldSelector')
|
43
|
+
end
|
44
|
+
end
|
36
45
|
end
|
@@ -4,7 +4,7 @@ describe Attributor::HashDSLCompiler do
|
|
4
4
|
let(:target) { double('model', attributes: {}) }
|
5
5
|
|
6
6
|
let(:dsl_compiler_options) { {} }
|
7
|
-
subject(:dsl_compiler) { Attributor::HashDSLCompiler.new(target, dsl_compiler_options) }
|
7
|
+
subject(:dsl_compiler) { Attributor::HashDSLCompiler.new(target, **dsl_compiler_options) }
|
8
8
|
|
9
9
|
it 'returns the requirements DSL attached to the right target' do
|
10
10
|
req_dsl = dsl_compiler._requirements_dsl
|
@@ -103,7 +103,7 @@ describe Attributor::HashDSLCompiler do
|
|
103
103
|
end
|
104
104
|
|
105
105
|
context 'Requirement#validate' do
|
106
|
-
let(:requirement) { req_class.new(arguments) }
|
106
|
+
let(:requirement) { req_class.new(**arguments) }
|
107
107
|
let(:subject) { requirement.validate(value, ['$'], nil) }
|
108
108
|
|
109
109
|
context 'for :all' 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
|