attributor 5.0.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +30 -0
- data/.travis.yml +6 -4
- data/CHANGELOG.md +6 -1
- data/Gemfile +1 -1
- data/Guardfile +14 -8
- data/Rakefile +4 -5
- data/attributor.gemspec +34 -29
- data/lib/attributor.rb +23 -29
- data/lib/attributor/attribute.rb +108 -127
- data/lib/attributor/attribute_resolver.rb +12 -26
- data/lib/attributor/dsl_compiler.rb +17 -21
- data/lib/attributor/dumpable.rb +1 -2
- data/lib/attributor/example_mixin.rb +5 -8
- data/lib/attributor/exceptions.rb +5 -6
- data/lib/attributor/extensions/randexp.rb +3 -5
- data/lib/attributor/extras/field_selector.rb +4 -4
- data/lib/attributor/extras/field_selector/transformer.rb +6 -7
- data/lib/attributor/families/numeric.rb +0 -2
- data/lib/attributor/families/temporal.rb +1 -4
- data/lib/attributor/hash_dsl_compiler.rb +22 -25
- data/lib/attributor/type.rb +24 -32
- data/lib/attributor/types/bigdecimal.rb +7 -14
- data/lib/attributor/types/boolean.rb +5 -8
- data/lib/attributor/types/class.rb +9 -10
- data/lib/attributor/types/collection.rb +34 -44
- data/lib/attributor/types/container.rb +9 -15
- data/lib/attributor/types/csv.rb +7 -10
- data/lib/attributor/types/date.rb +20 -25
- data/lib/attributor/types/date_time.rb +7 -14
- data/lib/attributor/types/float.rb +4 -6
- data/lib/attributor/types/hash.rb +171 -196
- data/lib/attributor/types/ids.rb +2 -6
- data/lib/attributor/types/integer.rb +12 -17
- data/lib/attributor/types/model.rb +39 -48
- data/lib/attributor/types/object.rb +2 -4
- data/lib/attributor/types/polymorphic.rb +118 -0
- data/lib/attributor/types/regexp.rb +4 -5
- data/lib/attributor/types/string.rb +6 -7
- data/lib/attributor/types/struct.rb +8 -15
- data/lib/attributor/types/symbol.rb +3 -6
- data/lib/attributor/types/tempfile.rb +5 -6
- data/lib/attributor/types/time.rb +11 -11
- data/lib/attributor/types/uri.rb +9 -10
- data/lib/attributor/version.rb +1 -1
- data/spec/attribute_resolver_spec.rb +57 -78
- data/spec/attribute_spec.rb +174 -216
- data/spec/attributor_spec.rb +11 -15
- data/spec/dsl_compiler_spec.rb +19 -33
- data/spec/dumpable_spec.rb +6 -7
- data/spec/extras/field_selector/field_selector_spec.rb +1 -1
- data/spec/families_spec.rb +1 -3
- data/spec/hash_dsl_compiler_spec.rb +65 -74
- data/spec/spec_helper.rb +9 -3
- data/spec/support/hashes.rb +2 -3
- data/spec/support/models.rb +30 -36
- data/spec/support/polymorphics.rb +10 -0
- data/spec/type_spec.rb +38 -61
- data/spec/types/bigdecimal_spec.rb +11 -15
- data/spec/types/boolean_spec.rb +12 -39
- data/spec/types/class_spec.rb +10 -11
- data/spec/types/collection_spec.rb +72 -81
- data/spec/types/container_spec.rb +22 -26
- data/spec/types/csv_spec.rb +15 -16
- data/spec/types/date_spec.rb +16 -33
- data/spec/types/date_time_spec.rb +16 -33
- data/spec/types/file_upload_spec.rb +1 -2
- data/spec/types/float_spec.rb +7 -14
- data/spec/types/hash_spec.rb +285 -289
- data/spec/types/ids_spec.rb +5 -7
- data/spec/types/integer_spec.rb +37 -46
- data/spec/types/model_spec.rb +111 -128
- data/spec/types/polymorphic_spec.rb +134 -0
- data/spec/types/regexp_spec.rb +4 -7
- data/spec/types/string_spec.rb +17 -21
- data/spec/types/struct_spec.rb +40 -47
- data/spec/types/tempfile_spec.rb +1 -2
- data/spec/types/temporal_spec.rb +9 -0
- data/spec/types/time_spec.rb +16 -32
- data/spec/types/type_spec.rb +15 -0
- data/spec/types/uri_spec.rb +6 -7
- metadata +77 -25
@@ -1,11 +1,9 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
|
5
|
-
|
6
4
|
class AttributeResolver
|
7
5
|
ROOT_PREFIX = '$'.freeze
|
8
|
-
COLLECTION_INDEX_KEY = /^at\((\d+)\)
|
6
|
+
COLLECTION_INDEX_KEY = /^at\((\d+)\)$/
|
9
7
|
|
10
8
|
class Data < ::Hash
|
11
9
|
include Hashie::Extensions::MethodReader
|
@@ -17,8 +15,7 @@ module Attributor
|
|
17
15
|
@data = Data.new
|
18
16
|
end
|
19
17
|
|
20
|
-
|
21
|
-
def query!(key_path, path_prefix=ROOT_PREFIX)
|
18
|
+
def query!(key_path, path_prefix = ROOT_PREFIX)
|
22
19
|
# If the incoming key_path is not an absolute path, append the given prefix
|
23
20
|
# NOTE: Need to index key_path by range here because Ruby 1.8 returns a
|
24
21
|
# FixNum for the ASCII code, not the actual character, when indexing by a number.
|
@@ -49,7 +46,6 @@ module Attributor
|
|
49
46
|
result
|
50
47
|
end
|
51
48
|
|
52
|
-
|
53
49
|
# Query for a certain key in the attribute hierarchy
|
54
50
|
#
|
55
51
|
# @param [String] key_path The name of the key to query and its path
|
@@ -57,21 +53,20 @@ module Attributor
|
|
57
53
|
#
|
58
54
|
# @return [String] The value of the specified attribute/key
|
59
55
|
#
|
60
|
-
def query(key_path,path_prefix=ROOT_PREFIX)
|
61
|
-
query!(key_path,path_prefix)
|
62
|
-
rescue NoMethodError
|
56
|
+
def query(key_path, path_prefix = ROOT_PREFIX)
|
57
|
+
query!(key_path, path_prefix)
|
58
|
+
rescue NoMethodError
|
63
59
|
nil
|
64
60
|
end
|
65
61
|
|
66
62
|
def register(key_path, value)
|
67
63
|
if key_path.split(SEPARATOR).size > 1
|
68
|
-
raise AttributorException
|
64
|
+
raise AttributorException, "can only register top-level attributes. got: #{key_path}"
|
69
65
|
end
|
70
66
|
|
71
67
|
@data[key_path] = value
|
72
68
|
end
|
73
69
|
|
74
|
-
|
75
70
|
# Checks that the the condition is met. This means the attribute identified
|
76
71
|
# by path_prefix and key_path satisfies the optional predicate, which when
|
77
72
|
# nil simply checks for existence.
|
@@ -84,13 +79,11 @@ module Attributor
|
|
84
79
|
#
|
85
80
|
# @raise [AttributorException] When an unsupported predicate is passed
|
86
81
|
#
|
87
|
-
def check(path_prefix, key_path, predicate=nil)
|
88
|
-
value =
|
82
|
+
def check(path_prefix, key_path, predicate = nil)
|
83
|
+
value = query(key_path, path_prefix)
|
89
84
|
|
90
85
|
# we have a value, any value, which is good enough given no predicate
|
91
|
-
if !value.nil? && predicate.nil?
|
92
|
-
return true
|
93
|
-
end
|
86
|
+
return true if !value.nil? && predicate.nil?
|
94
87
|
|
95
88
|
case predicate
|
96
89
|
when ::String, ::Regexp, ::Integer, ::Float, ::DateTime, true, false
|
@@ -101,9 +94,8 @@ module Attributor
|
|
101
94
|
when nil
|
102
95
|
return !value.nil?
|
103
96
|
else
|
104
|
-
raise AttributorException
|
97
|
+
raise AttributorException, "predicate not supported: #{predicate.inspect}"
|
105
98
|
end
|
106
|
-
|
107
99
|
end
|
108
100
|
|
109
101
|
# TODO: kill this when we also kill Taylor's IdentityMap.current
|
@@ -111,15 +103,9 @@ module Attributor
|
|
111
103
|
Thread.current[:_attributor_attribute_resolver] = resolver
|
112
104
|
end
|
113
105
|
|
114
|
-
|
115
106
|
def self.current
|
116
|
-
|
117
|
-
|
118
|
-
else
|
119
|
-
raise AttributorException, "No AttributeResolver set."
|
120
|
-
end
|
107
|
+
raise AttributorException, 'No AttributeResolver set.' unless Thread.current[:_attributor_attribute_resolver]
|
108
|
+
Thread.current[:_attributor_attribute_resolver]
|
121
109
|
end
|
122
|
-
|
123
110
|
end
|
124
|
-
|
125
111
|
end
|
@@ -1,6 +1,5 @@
|
|
1
|
-
#Container of options and structure definition
|
1
|
+
# Container of options and structure definition
|
2
2
|
module Attributor
|
3
|
-
|
4
3
|
# RULES FOR ATTRIBUTES
|
5
4
|
# The type of an attribute is:
|
6
5
|
# the specified type
|
@@ -11,7 +10,6 @@ module Attributor
|
|
11
10
|
# The reference option for an attribute is passed if a block is given
|
12
11
|
|
13
12
|
class DSLCompiler
|
14
|
-
|
15
13
|
attr_accessor :options, :target
|
16
14
|
|
17
15
|
def initialize(target, **options)
|
@@ -21,7 +19,7 @@ module Attributor
|
|
21
19
|
|
22
20
|
def parse(*blocks)
|
23
21
|
blocks.push(Proc.new) if block_given?
|
24
|
-
blocks.each { |block|
|
22
|
+
blocks.each { |block| instance_eval(&block) }
|
25
23
|
self
|
26
24
|
end
|
27
25
|
|
@@ -33,19 +31,19 @@ module Attributor
|
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
def attribute(name, attr_type=nil, **opts, &block)
|
37
|
-
raise AttributorException, "Attribute names must be symbols, got: #{name.inspect}" unless name.
|
34
|
+
def attribute(name, attr_type = nil, **opts, &block)
|
35
|
+
raise AttributorException, "Attribute names must be symbols, got: #{name.inspect}" unless name.is_a? ::Symbol
|
38
36
|
target.attributes[name] = define(name, attr_type, **opts, &block)
|
39
37
|
end
|
40
38
|
|
41
|
-
def key(name, attr_type=nil, **opts, &block)
|
42
|
-
unless name.
|
39
|
+
def key(name, attr_type = nil, **opts, &block)
|
40
|
+
unless name.is_a?(options.fetch(:key_type, Attributor::Object).native_type)
|
43
41
|
raise "Invalid key: #{name.inspect}, must be instance of #{options[:key_type].native_type.name}"
|
44
42
|
end
|
45
43
|
target.keys[name] = define(name, attr_type, **opts, &block)
|
46
44
|
end
|
47
45
|
|
48
|
-
def extra(name, attr_type=nil, **opts, &block)
|
46
|
+
def extra(name, attr_type = nil, **opts, &block)
|
49
47
|
if attr_type.nil?
|
50
48
|
attr_type = Attributor::Hash.of(key: target.key_type, value: target.value_type)
|
51
49
|
end
|
@@ -65,7 +63,7 @@ module Attributor
|
|
65
63
|
# @param [Hash] opts describe opts param
|
66
64
|
# @param [Block] block describe block param
|
67
65
|
# @example
|
68
|
-
# attribute :email, String, example:
|
66
|
+
# attribute :email, String, example: Randgen.email
|
69
67
|
# @overload define(name, opts, &block)
|
70
68
|
# Assume a type of Attributor::Struct
|
71
69
|
# @param [symbol] name describe name param
|
@@ -79,7 +77,7 @@ module Attributor
|
|
79
77
|
# attribute :state, String
|
80
78
|
# end
|
81
79
|
# @api semiprivate
|
82
|
-
def define(name, attr_type=nil, **opts, &block)
|
80
|
+
def define(name, attr_type = nil, **opts, &block)
|
83
81
|
# add to existing attribute if present
|
84
82
|
if (existing_attribute = attributes[name])
|
85
83
|
if existing_attribute.attributes
|
@@ -89,8 +87,8 @@ module Attributor
|
|
89
87
|
end
|
90
88
|
|
91
89
|
# determine inherited attribute
|
92
|
-
inherited_attribute = nil
|
93
|
-
if (reference =
|
90
|
+
inherited_attribute = nil
|
91
|
+
if (reference = options[:reference])
|
94
92
|
if (inherited_attribute = reference.attributes[name])
|
95
93
|
opts = inherited_attribute.options.merge(opts) unless attr_type
|
96
94
|
opts[:reference] = inherited_attribute.type if block_given?
|
@@ -101,12 +99,12 @@ module Attributor
|
|
101
99
|
if attr_type.nil?
|
102
100
|
if block_given?
|
103
101
|
attr_type = if inherited_attribute && inherited_attribute.type < Attributor::Collection
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
102
|
+
# override the reference to be the member_attribute's type for collections
|
103
|
+
opts[:reference] = inherited_attribute.type.member_attribute.type
|
104
|
+
Attributor::Collection.of(Struct)
|
105
|
+
else
|
106
|
+
Attributor::Struct
|
107
|
+
end
|
110
108
|
elsif inherited_attribute
|
111
109
|
attr_type = inherited_attribute.type
|
112
110
|
else
|
@@ -116,7 +114,5 @@ module Attributor
|
|
116
114
|
|
117
115
|
Attributor::Attribute.new(attr_type, opts, &block)
|
118
116
|
end
|
119
|
-
|
120
|
-
|
121
117
|
end
|
122
118
|
end
|
data/lib/attributor/dumpable.rb
CHANGED
@@ -2,11 +2,9 @@
|
|
2
2
|
# primarily enables support for lazy values.
|
3
3
|
|
4
4
|
module Attributor
|
5
|
-
|
6
5
|
module ExampleMixin
|
7
|
-
|
8
6
|
def self.extended(obj)
|
9
|
-
if obj.
|
7
|
+
if obj.is_a? Attributor::Model
|
10
8
|
obj.class.attributes.each do |name, _|
|
11
9
|
obj.define_singleton_method(name) do
|
12
10
|
get(name)
|
@@ -32,7 +30,7 @@ module Attributor
|
|
32
30
|
@contents[k]
|
33
31
|
end
|
34
32
|
|
35
|
-
def []=(k,v)
|
33
|
+
def []=(k, v)
|
36
34
|
lazy_attributes.delete k
|
37
35
|
@contents[k] = v
|
38
36
|
end
|
@@ -41,7 +39,7 @@ module Attributor
|
|
41
39
|
contents.each(&block)
|
42
40
|
end
|
43
41
|
|
44
|
-
|
42
|
+
alias each_pair each
|
45
43
|
|
46
44
|
def values
|
47
45
|
contents.values
|
@@ -63,7 +61,7 @@ module Attributor
|
|
63
61
|
@contents.key?(key) || lazy_attributes.key?(key)
|
64
62
|
end
|
65
63
|
|
66
|
-
def get(key, context:
|
64
|
+
def get(key, context: generate_subcontext(Attributor::DEFAULT_ROOT_CONTEXT, key))
|
67
65
|
key = self.class.key_attribute.load(key, context)
|
68
66
|
|
69
67
|
unless @contents.key? key
|
@@ -78,7 +76,7 @@ module Attributor
|
|
78
76
|
|
79
77
|
def attributes
|
80
78
|
lazy_attributes.keys.each do |name|
|
81
|
-
|
79
|
+
__send__(name)
|
82
80
|
end
|
83
81
|
|
84
82
|
super
|
@@ -93,5 +91,4 @@ module Attributor
|
|
93
91
|
super
|
94
92
|
end
|
95
93
|
end
|
96
|
-
|
97
94
|
end
|
@@ -6,14 +6,13 @@ module Attributor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class IncompatibleTypeError < LoadError
|
9
|
-
|
10
|
-
def initialize(type:, value_type: , context: )
|
9
|
+
def initialize(type:, value_type:, context:)
|
11
10
|
super "Type #{type} cannot load values of type #{value_type} while loading #{Attributor.humanize_context(context)}."
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
15
14
|
class CoercionError < LoadError
|
16
|
-
def initialize(
|
15
|
+
def initialize(context:, from:, to:, value: nil)
|
17
16
|
msg = "Error coercing from #{from} to #{to} while loading #{Attributor.humanize_context(context)}."
|
18
17
|
msg += " Received value #{Attributor.errorize_value(value)}" if value
|
19
18
|
super msg
|
@@ -21,7 +20,7 @@ module Attributor
|
|
21
20
|
end
|
22
21
|
|
23
22
|
class DeserializationError < LoadError
|
24
|
-
def initialize(
|
23
|
+
def initialize(context:, from:, encoding:, value: nil)
|
25
24
|
msg = "Error deserializing a #{from} using #{encoding} while loading #{Attributor.humanize_context(context)}."
|
26
25
|
msg += " Received value #{Attributor.errorize_value(value)}" if value
|
27
26
|
super msg
|
@@ -29,10 +28,10 @@ module Attributor
|
|
29
28
|
end
|
30
29
|
|
31
30
|
class DumpError < AttributorException
|
32
|
-
def initialize(
|
31
|
+
def initialize(context:, name:, type:, original_exception:)
|
33
32
|
msg = "Error while dumping attribute #{name} of type #{type} for context #{Attributor.humanize_context(context)}."
|
34
33
|
msg << " Reason: #{original_exception}"
|
35
34
|
super msg
|
36
35
|
end
|
37
36
|
end
|
38
|
-
end
|
37
|
+
end
|
@@ -1,15 +1,13 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
|
-
|
4
3
|
class Randgen
|
5
4
|
DATE_TIME_EPOCH = ::DateTime.new(2015, 1, 1, 0, 0, 0)
|
6
5
|
|
7
6
|
def self.date
|
8
|
-
|
7
|
+
DATE_TIME_EPOCH - rand(800)
|
9
8
|
end
|
10
9
|
|
11
10
|
def self.time
|
12
|
-
|
11
|
+
date.to_time
|
13
12
|
end
|
14
|
-
|
15
|
-
end
|
13
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
begin
|
2
2
|
require 'parslet'
|
3
3
|
rescue LoadError
|
4
|
-
warn "Attributor::FieldSelector requires the 'parslet' gem, which can not be found. "
|
5
|
-
|
4
|
+
warn "Attributor::FieldSelector requires the 'parslet' gem, which can not be found. " \
|
5
|
+
"Please make sure it's in your Gemfile or installed in your system."
|
6
6
|
end
|
7
7
|
|
8
8
|
module Attributor
|
@@ -16,7 +16,7 @@ module Attributor
|
|
16
16
|
::Hash
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.example(_context = nil,
|
19
|
+
def self.example(_context = nil, options: {})
|
20
20
|
3.times.each_with_object([]) do |_i, array|
|
21
21
|
array << /\w{5,8}/.gen
|
22
22
|
end.join(',')
|
@@ -24,7 +24,7 @@ module Attributor
|
|
24
24
|
|
25
25
|
def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
|
26
26
|
return nil if value.nil?
|
27
|
-
return value if
|
27
|
+
return value if valid_type? value
|
28
28
|
return {} if value.empty?
|
29
29
|
|
30
30
|
parsed = Parser.new.parse(value)
|
@@ -1,15 +1,14 @@
|
|
1
1
|
module Attributor
|
2
2
|
class FieldSelector
|
3
3
|
class Transformer < Parslet::Transform
|
4
|
-
|
5
4
|
rule(field: simple(:field_token), children: subtree(:children_tree)) do
|
6
5
|
cs = if children_tree.empty?
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
true
|
7
|
+
else
|
8
|
+
children_tree.each_with_object({}) do |item, hash|
|
9
|
+
hash.merge! item
|
10
|
+
end
|
11
|
+
end
|
13
12
|
{ field_token.to_sym => cs }
|
14
13
|
end
|
15
14
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# Abstract type for the 'temporal' family
|
2
2
|
|
3
3
|
module Attributor
|
4
|
-
|
5
4
|
class Temporal
|
6
5
|
include Type
|
7
6
|
|
@@ -13,10 +12,8 @@ module Attributor
|
|
13
12
|
'temporal'
|
14
13
|
end
|
15
14
|
|
16
|
-
def self.dump(value
|
15
|
+
def self.dump(value, **_opts)
|
17
16
|
value && value.iso8601
|
18
17
|
end
|
19
|
-
|
20
|
-
|
21
18
|
end
|
22
19
|
end
|
@@ -1,10 +1,7 @@
|
|
1
1
|
require_relative 'dsl_compiler'
|
2
2
|
|
3
|
-
|
4
3
|
module Attributor
|
5
|
-
|
6
4
|
class HashDSLCompiler < DSLCompiler
|
7
|
-
|
8
5
|
# A class that encapsulates the definition of a requirement for Hash attributes
|
9
6
|
# It implements the validation against incoming values and it describes its format for documentation purposes
|
10
7
|
class Requirement
|
@@ -18,9 +15,9 @@ module Attributor
|
|
18
15
|
@type = spec.keys.first
|
19
16
|
case type
|
20
17
|
when :all
|
21
|
-
|
18
|
+
of(*spec[type])
|
22
19
|
when :exclusive
|
23
|
-
|
20
|
+
of(*spec[type])
|
24
21
|
else
|
25
22
|
@number = spec[type]
|
26
23
|
end
|
@@ -31,7 +28,7 @@ module Attributor
|
|
31
28
|
self
|
32
29
|
end
|
33
30
|
|
34
|
-
def validate(keys,context=Attributor::DEFAULT_ROOT_CONTEXT,_attribute=nil)
|
31
|
+
def validate(keys, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
|
35
32
|
result = []
|
36
33
|
case type
|
37
34
|
when :all
|
@@ -49,13 +46,13 @@ module Attributor
|
|
49
46
|
when :at_most
|
50
47
|
rest = attr_names & keys
|
51
48
|
if rest.size > number
|
52
|
-
found = rest.empty? ?
|
49
|
+
found = rest.empty? ? 'none' : rest.inspect
|
53
50
|
result.push "At most #{number} keys out of #{attr_names} can be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
|
54
51
|
end
|
55
52
|
when :at_least
|
56
53
|
rest = attr_names & keys
|
57
54
|
if rest.size < number
|
58
|
-
found = rest.empty? ?
|
55
|
+
found = rest.empty? ? 'none' : rest.inspect
|
59
56
|
result.push "At least #{number} keys out of #{attr_names} are required to be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
|
60
57
|
end
|
61
58
|
when :exclusive
|
@@ -67,15 +64,14 @@ module Attributor
|
|
67
64
|
result
|
68
65
|
end
|
69
66
|
|
70
|
-
def describe(
|
71
|
-
hash = {type: type, attributes: attr_names}
|
67
|
+
def describe(_shallow = false, _example: nil)
|
68
|
+
hash = { type: type, attributes: attr_names }
|
72
69
|
hash[:count] = number unless number.nil?
|
73
70
|
hash[:description] = description unless description.nil?
|
74
71
|
hash
|
75
72
|
end
|
76
73
|
end
|
77
74
|
|
78
|
-
|
79
75
|
# A class that encapsulates the available DSL under the `requires` keyword.
|
80
76
|
# In particular it allows to define requirements like:
|
81
77
|
# requires.all :attr1, :attr2, :attr3
|
@@ -91,28 +87,33 @@ module Attributor
|
|
91
87
|
self.target = target
|
92
88
|
self.options = opts
|
93
89
|
end
|
90
|
+
|
94
91
|
def all(*attr_names, **opts)
|
95
|
-
req = Requirement.new(
|
92
|
+
req = Requirement.new(options.merge(opts).merge(all: attr_names))
|
96
93
|
target.add_requirement req
|
97
94
|
req
|
98
95
|
end
|
96
|
+
|
99
97
|
def at_most(number)
|
100
|
-
req = Requirement.new(
|
98
|
+
req = Requirement.new(options.merge(at_most: number))
|
101
99
|
target.add_requirement req
|
102
100
|
req
|
103
101
|
end
|
102
|
+
|
104
103
|
def at_least(number)
|
105
|
-
req = Requirement.new(
|
104
|
+
req = Requirement.new(options.merge(at_least: number))
|
106
105
|
target.add_requirement req
|
107
106
|
req
|
108
107
|
end
|
108
|
+
|
109
109
|
def exactly(number)
|
110
|
-
req = Requirement.new(
|
110
|
+
req = Requirement.new(options.merge(exactly: number))
|
111
111
|
target.add_requirement req
|
112
112
|
req
|
113
113
|
end
|
114
|
+
|
114
115
|
def exclusive(*attr_names, **opts)
|
115
|
-
req = Requirement.new(
|
116
|
+
req = Requirement.new(options.merge(opts).merge(exclusive: attr_names))
|
116
117
|
target.add_requirement req
|
117
118
|
req
|
118
119
|
end
|
@@ -122,21 +123,17 @@ module Attributor
|
|
122
123
|
@requirements_dsl ||= RequiresDSL.new(@target)
|
123
124
|
end
|
124
125
|
|
125
|
-
def requires(*spec
|
126
|
+
def requires(*spec, **opts, &block)
|
126
127
|
if spec.empty?
|
127
|
-
unless opts.empty?
|
128
|
-
self._requirements_dsl.options.merge(opts)
|
129
|
-
end
|
128
|
+
_requirements_dsl.options.merge(opts) unless opts.empty?
|
130
129
|
if block_given?
|
131
|
-
|
130
|
+
_requirements_dsl.instance_eval(&block)
|
132
131
|
else
|
133
|
-
|
132
|
+
_requirements_dsl
|
134
133
|
end
|
135
134
|
else
|
136
|
-
|
135
|
+
_requirements_dsl.all(*spec, opts)
|
137
136
|
end
|
138
137
|
end
|
139
|
-
|
140
|
-
|
141
138
|
end
|
142
139
|
end
|