attestor 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +36 -48
- data/lib/attestor.rb +2 -2
- data/lib/attestor/validations.rb +28 -12
- data/lib/attestor/validations/{item.rb → validator.rb} +34 -5
- data/lib/attestor/validations/{collection.rb → validators.rb} +16 -16
- data/lib/attestor/version.rb +1 -1
- data/spec/features/example_spec.rb +109 -0
- data/spec/tests/validations/{item_spec.rb → validator_spec.rb} +69 -3
- data/spec/tests/validations/{collection_spec.rb → validators_spec.rb} +19 -19
- data/spec/tests/validations_spec.rb +73 -32
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5d5fbaf26c97f5c5dd838289b64904714d118b7
|
4
|
+
data.tar.gz: 777f4abaa5ef17cfe19627cbb69b9e960394a4c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91138866beb8ef0615181d4ab9c3a9cbad86f3bb2b238b648f155b0b7fea5d6a1b2b48185762e7b5289b66965faa5f7bc9c62c49e85db2705744ae28f4bdfd42
|
7
|
+
data.tar.gz: 3ff1370688ca7712e5e7fdfad088e8298f73e10a85f17e247923cec2e05ed36f0791604342c1a3ae77c481c9d702710a90861b5d80640b5464910280582072e4
|
data/README.md
CHANGED
@@ -39,8 +39,6 @@ Approach
|
|
39
39
|
|
40
40
|
Instead of collecting errors inside the object, the module's `validate` instance method just raises an exception (`Attestor::InvalidError`), that carries errors outside of the object. The object stays untouched (and can be made immutable).
|
41
41
|
|
42
|
-
So to speak, validation just attests at the object and complains loudly when things goes wrong.
|
43
|
-
|
44
42
|
Installation
|
45
43
|
------------
|
46
44
|
|
@@ -66,8 +64,6 @@ gem install attestor
|
|
66
64
|
Basic Use
|
67
65
|
----------
|
68
66
|
|
69
|
-
`Attestor::Validations` API consists of 1 class method `.validate` and 2 instance methods (`validate` and `invalid`).
|
70
|
-
|
71
67
|
Declare validation in the same way as ActiveModel's `.validate` method does:
|
72
68
|
|
73
69
|
```ruby
|
@@ -95,11 +91,12 @@ end
|
|
95
91
|
|
96
92
|
The `#invalid` method translates its argument in a current class scope and raises an exception.
|
97
93
|
|
98
|
-
```
|
94
|
+
```yaml
|
99
95
|
# config/locales/en.yml
|
96
|
+
---
|
100
97
|
en:
|
101
98
|
attestor:
|
102
|
-
|
99
|
+
errors:
|
103
100
|
transfer:
|
104
101
|
inconsistent: "Credit differs from debet by %{fraud}"
|
105
102
|
```
|
@@ -120,7 +117,7 @@ rescue => error
|
|
120
117
|
end
|
121
118
|
```
|
122
119
|
|
123
|
-
|
120
|
+
Use of Contexts
|
124
121
|
---------------
|
125
122
|
|
126
123
|
Sometimes you need to validate the object agaist the subset of validations, not all of them.
|
@@ -142,17 +139,17 @@ fraud_transfer.validate # => InvalidError
|
|
142
139
|
fraud_transfer.validate :steal_of_money # => PASSES!
|
143
140
|
```
|
144
141
|
|
145
|
-
|
142
|
+
You can use the same validator several times with different contexts. Any validation will be made independently from the others:
|
146
143
|
|
147
144
|
```ruby
|
148
|
-
class Transfer
|
149
|
-
|
150
|
-
|
145
|
+
class Transfer
|
146
|
+
# ...
|
151
147
|
validate :consistent, only: :fair_trade
|
152
|
-
|
148
|
+
validate :consistent, only: :legal
|
153
149
|
|
154
|
-
|
155
|
-
|
150
|
+
# This is the same as:
|
151
|
+
# validate :consistent, only: [:fair_trade, :legal]
|
152
|
+
end
|
156
153
|
```
|
157
154
|
|
158
155
|
Policy Objects
|
@@ -162,7 +159,7 @@ Extract a validator to the separate object (policy). Basically the policy includ
|
|
162
159
|
|
163
160
|
```ruby
|
164
161
|
class ConsistencyPolicy < Struct.new(:debet, :credit)
|
165
|
-
include Attestor::Policy
|
162
|
+
include Attestor::Policy # includes Attestor::Validator as well
|
166
163
|
|
167
164
|
validate :consistent
|
168
165
|
|
@@ -179,33 +176,40 @@ This looks mainly the same as before. But the policy's debet and credit are numb
|
|
179
176
|
|
180
177
|
This is the core part of the [Policy Object design pattern] - it isolates the rule from unsignificant details of the target.
|
181
178
|
|
182
|
-
|
179
|
+
When you have a policy to follow, use the `follow_policy` method:
|
183
180
|
|
184
181
|
```ruby
|
185
182
|
class Transfer < Struct.new(:debet, :credit)
|
186
183
|
include Attestor::Validations
|
187
184
|
|
188
|
-
|
185
|
+
follow_policy :consistency, only: :fair_trade
|
189
186
|
|
190
187
|
private
|
191
188
|
|
192
|
-
def
|
193
|
-
|
194
|
-
invalid :inconsistent if policy.invalid?
|
189
|
+
def consistency # should respond to #valid? and #invalid?
|
190
|
+
ConsistencyPolicy.new(debet.sum, credit.sum)
|
195
191
|
end
|
196
192
|
end
|
197
193
|
```
|
198
194
|
|
199
|
-
|
195
|
+
In case the policy object is invalid, validation raises an exception.
|
200
196
|
|
201
|
-
|
197
|
+
The name of the method (`consistency`) will be used for the error message:
|
202
198
|
|
203
|
-
|
199
|
+
```yaml
|
200
|
+
# config/locales/en.yml
|
201
|
+
---
|
202
|
+
en:
|
203
|
+
attestor:
|
204
|
+
errors:
|
205
|
+
transfer:
|
206
|
+
consistency: "The transfer is inconsistent"
|
207
|
+
```
|
204
208
|
|
205
209
|
Complex Policies
|
206
210
|
----------------
|
207
211
|
|
208
|
-
Now that
|
212
|
+
Now that policies are isolated from their targets, we can provide complex policies from simpler ones.
|
209
213
|
|
210
214
|
Suppose we have two policy objects:
|
211
215
|
|
@@ -214,7 +218,7 @@ valid_policy.valid? # => true
|
|
214
218
|
invalid_policy.valid? # => false
|
215
219
|
```
|
216
220
|
|
217
|
-
Use
|
221
|
+
Use factory methods to provide compositions:
|
218
222
|
|
219
223
|
```ruby
|
220
224
|
complex_policy = valid_policy.not
|
@@ -233,7 +237,7 @@ complex_policy = valid_policy.xor(valid_poicy, invalid_policy)
|
|
233
237
|
complex_policy.validate # => passes
|
234
238
|
```
|
235
239
|
|
236
|
-
The `or`, `and` and `xor` methods
|
240
|
+
The `or`, `and` and `xor` methods called without argument(s) don't provide a policy object. They return lazy composer, expecting `#not` method.
|
237
241
|
|
238
242
|
```ruby
|
239
243
|
complex_policy = valid_policy.and.not(invalid_policy, invalid_policy)
|
@@ -245,13 +249,13 @@ If you prefer wrapping to chaining, use the `Policy` factory methods instead:
|
|
245
249
|
|
246
250
|
```ruby
|
247
251
|
Policy.and(valid_policy, invalid_policy)
|
248
|
-
# this is the same as: valid_policy.and
|
252
|
+
# this is the same as: valid_policy.and(invalid_policy)
|
249
253
|
|
250
254
|
Policy.or(valid_policy, invalid_policy)
|
251
|
-
# this is the same as: valid_policy.or
|
255
|
+
# this is the same as: valid_policy.or(invalid_policy)
|
252
256
|
|
253
257
|
Policy.xor(valid_policy, invalid_policy)
|
254
|
-
# this is the same as: valid_policy.xor
|
258
|
+
# this is the same as: valid_policy.xor(invalid_policy)
|
255
259
|
|
256
260
|
Policy.not(valid_policy)
|
257
261
|
# this is the same as: valid_policy.not
|
@@ -259,25 +263,6 @@ Policy.not(valid_policy)
|
|
259
263
|
|
260
264
|
As before, you can use any number of policies (except for negation of a single policy) at any number of nesting.
|
261
265
|
|
262
|
-
This can be used either in targets or in complex policies. In the later case do it like this:
|
263
|
-
|
264
|
-
```ruby
|
265
|
-
class ComplexPolicy < Struct.new(:a, :b, :c)
|
266
|
-
include Attestor::Policy
|
267
|
-
|
268
|
-
validate :complex_rule
|
269
|
-
|
270
|
-
private
|
271
|
-
|
272
|
-
def complex_rule
|
273
|
-
first_policy = FirstPolicy.new(a, b)
|
274
|
-
second_policy = SecondPolicy.new(b, c)
|
275
|
-
|
276
|
-
invalid :base unless first_policy.xor(second_policy).valid?
|
277
|
-
end
|
278
|
-
end
|
279
|
-
```
|
280
|
-
|
281
266
|
Compatibility
|
282
267
|
-------------
|
283
268
|
|
@@ -289,6 +274,9 @@ Tested under rubies compatible to rubies with API 2.0+:
|
|
289
274
|
|
290
275
|
Uses [RSpec] 3.0+ for testing and [hexx-suit] for dev/test tools collection.
|
291
276
|
|
277
|
+
[RSpec]: http://rspec.info
|
278
|
+
[hexx-suit]: https://github.com/nepalez/hexx-suit
|
279
|
+
|
292
280
|
Contributing
|
293
281
|
------------
|
294
282
|
|
data/lib/attestor.rb
CHANGED
@@ -7,8 +7,8 @@ require_relative "attestor/version"
|
|
7
7
|
require_relative "attestor/invalid_error"
|
8
8
|
|
9
9
|
require_relative "attestor/validations"
|
10
|
-
require_relative "attestor/validations/
|
11
|
-
require_relative "attestor/validations/
|
10
|
+
require_relative "attestor/validations/validator"
|
11
|
+
require_relative "attestor/validations/validators"
|
12
12
|
require_relative "attestor/validations/message"
|
13
13
|
|
14
14
|
require_relative "attestor/policy/factory"
|
data/lib/attestor/validations.rb
CHANGED
@@ -5,14 +5,14 @@ module Attestor
|
|
5
5
|
# API for objects to be validated
|
6
6
|
module Validations
|
7
7
|
|
8
|
-
# Calls all
|
8
|
+
# Calls all validators for given context
|
9
9
|
#
|
10
|
-
# @raise [Attestor::Validations::InvalidError] if
|
11
|
-
# @raise [NoMethodError] if some of
|
10
|
+
# @raise [Attestor::Validations::InvalidError] if validators fail
|
11
|
+
# @raise [NoMethodError] if some of validators are not implemented
|
12
12
|
#
|
13
13
|
# @return [undefined]
|
14
14
|
def validate(context = :all)
|
15
|
-
self.class.
|
15
|
+
self.class.validators.set(context).each { |item| item.validate(self) }
|
16
16
|
end
|
17
17
|
|
18
18
|
# Raises InvalidError with a corresponding message
|
@@ -42,18 +42,18 @@ module Attestor
|
|
42
42
|
# @private
|
43
43
|
module ClassMethods
|
44
44
|
|
45
|
-
# Returns a collection of
|
45
|
+
# Returns a collection of applied validators
|
46
46
|
#
|
47
|
-
# @return [Attestor::
|
47
|
+
# @return [Attestor::Validators]
|
48
48
|
#
|
49
49
|
# @api private
|
50
|
-
def
|
51
|
-
@
|
50
|
+
def validators
|
51
|
+
@validators ||= Validators.new
|
52
52
|
end
|
53
53
|
|
54
|
-
#
|
54
|
+
# Registers a validator
|
55
55
|
#
|
56
|
-
# Mutates the class by changing its {#
|
56
|
+
# Mutates the class by changing its {#validators} attribute!
|
57
57
|
#
|
58
58
|
# @param [#to_sym] name
|
59
59
|
# @param [Hash] options
|
@@ -62,9 +62,25 @@ module Attestor
|
|
62
62
|
# @option options [#to_sym, Array<#to_sym>] :only
|
63
63
|
# the white list of contexts for validation
|
64
64
|
#
|
65
|
-
# @return [Attestor::
|
65
|
+
# @return [Attestor::Validators] the updated collection
|
66
66
|
def validate(name, options = {})
|
67
|
-
@
|
67
|
+
@validators = validators.add(name, options)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Registers a followed policy
|
71
|
+
#
|
72
|
+
# Mutates the class by changing its {#validators} attribute!
|
73
|
+
#
|
74
|
+
# @param [#to_sym] name
|
75
|
+
# @param [Hash] options
|
76
|
+
# @option options [#to_sym, Array<#to_sym>] :except
|
77
|
+
# the black list of contexts for validation
|
78
|
+
# @option options [#to_sym, Array<#to_sym>] :only
|
79
|
+
# the white list of contexts for validation
|
80
|
+
#
|
81
|
+
# @return [Attestor::Collection] the updated collection
|
82
|
+
def follow_policy(name, options = {})
|
83
|
+
@validators = validators.add(name, options.merge(policy: true))
|
68
84
|
end
|
69
85
|
|
70
86
|
end # module ClassMethods
|
@@ -4,10 +4,16 @@ module Attestor
|
|
4
4
|
|
5
5
|
module Validations
|
6
6
|
|
7
|
-
#
|
7
|
+
# Describe a validator for class instances
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# validator = Validator.new(:foo, policy: :bar, only: :baz)
|
11
|
+
#
|
12
|
+
# validator.used_in_context? :baz # => true
|
13
|
+
# validator.validate object
|
8
14
|
#
|
9
15
|
# @api private
|
10
|
-
class
|
16
|
+
class Validator
|
11
17
|
|
12
18
|
# @!scope class
|
13
19
|
# @!method new(name, except: [], only: [])
|
@@ -17,11 +23,12 @@ module Attestor
|
|
17
23
|
# @option [#to_sym, Array<#to_sym>] :except
|
18
24
|
# @option [#to_sym, Array<#to_sym>] :only
|
19
25
|
#
|
20
|
-
# @return [Attestor::
|
26
|
+
# @return [Attestor::Validations::Validator]
|
21
27
|
|
22
28
|
# @private
|
23
|
-
def initialize(name, except: nil, only: nil)
|
29
|
+
def initialize(name, except: nil, only: nil, policy: nil)
|
24
30
|
@name = name.to_sym
|
31
|
+
@policy = policy
|
25
32
|
@whitelist = normalize(only)
|
26
33
|
@blacklist = normalize(except)
|
27
34
|
generate_id
|
@@ -30,9 +37,18 @@ module Attestor
|
|
30
37
|
|
31
38
|
# @!attribute [r] name
|
32
39
|
# The name of the item
|
40
|
+
#
|
33
41
|
# @return [Symbol]
|
34
42
|
attr_reader :name
|
35
43
|
|
44
|
+
# @!method policy?
|
45
|
+
# Whether the validator uses a policy
|
46
|
+
#
|
47
|
+
# @return [Boolean]
|
48
|
+
def policy?
|
49
|
+
@policy ? true : false
|
50
|
+
end
|
51
|
+
|
36
52
|
# Compares an item to another one
|
37
53
|
#
|
38
54
|
# @param [Object] other
|
@@ -52,6 +68,19 @@ module Attestor
|
|
52
68
|
whitelisted?(symbol) && !blacklisted?(symbol)
|
53
69
|
end
|
54
70
|
|
71
|
+
# Validates given object
|
72
|
+
#
|
73
|
+
# @param [Object] object
|
74
|
+
#
|
75
|
+
# @raise [Attestor::InvalidError]
|
76
|
+
# if object doesn't match validation rule
|
77
|
+
#
|
78
|
+
# @return [undefined]
|
79
|
+
def validate(object)
|
80
|
+
result = object.__send__(name)
|
81
|
+
object.__send__(:invalid, name) if policy? && result.invalid?
|
82
|
+
end
|
83
|
+
|
55
84
|
protected
|
56
85
|
|
57
86
|
# @!attribute [r] id
|
@@ -80,7 +109,7 @@ module Attestor
|
|
80
109
|
Array(list).map(&:to_sym).uniq
|
81
110
|
end
|
82
111
|
|
83
|
-
end # class
|
112
|
+
end # class Validator
|
84
113
|
|
85
114
|
end # module Validations
|
86
115
|
|
@@ -7,16 +7,16 @@ module Attestor
|
|
7
7
|
# The collection of validations used by class instances
|
8
8
|
#
|
9
9
|
# @api private
|
10
|
-
class
|
10
|
+
class Validators
|
11
11
|
include Enumerable
|
12
12
|
|
13
13
|
# @!scope class
|
14
14
|
# @!method new(items = [])
|
15
15
|
# Creates an immutable collection with optional list of items
|
16
16
|
#
|
17
|
-
# @param [Array<Attestor::
|
17
|
+
# @param [Array<Attestor::Validators::Validator>] items
|
18
18
|
#
|
19
|
-
# @return [Attestor::
|
19
|
+
# @return [Attestor::Validators]
|
20
20
|
|
21
21
|
# @private
|
22
22
|
def initialize(items = [])
|
@@ -27,46 +27,46 @@ module Attestor
|
|
27
27
|
# Iterates through the collection
|
28
28
|
#
|
29
29
|
# @yield the block
|
30
|
-
# @yieldparam [Attestor::
|
30
|
+
# @yieldparam [Attestor::Validators::Validator] item
|
31
31
|
# items from the collection
|
32
32
|
#
|
33
33
|
# @return [Enumerator]
|
34
34
|
def each
|
35
|
-
|
36
|
-
items.map(&:name).uniq.each { |item| yield(item) }
|
35
|
+
block_given? ? @items.each { |item| yield(item) } : to_enum
|
37
36
|
end
|
38
37
|
|
39
|
-
# Returns
|
38
|
+
# Returns validators updated by new item
|
40
39
|
#
|
41
40
|
# @param [#to_sym] name
|
42
41
|
# @param [Hash] options
|
43
42
|
# @option options [Array<#to_sym>] :except
|
44
43
|
# @option options [Array<#to_sym>] :only
|
44
|
+
# @option options [Symbol, nil] :policy
|
45
45
|
#
|
46
|
-
# @return [Attestor::
|
46
|
+
# @return [Attestor::Validators]
|
47
47
|
def add(name, options = {})
|
48
|
-
item =
|
49
|
-
return self if
|
48
|
+
item = Validator.new(name, options)
|
49
|
+
return self if include? item
|
50
50
|
|
51
|
-
self.class.new(items + [item])
|
51
|
+
self.class.new(@items + [item])
|
52
52
|
end
|
53
53
|
|
54
|
-
# Returns
|
54
|
+
# Returns validators used in given context
|
55
55
|
#
|
56
56
|
# @param [#to_sym] context
|
57
57
|
#
|
58
|
-
# @return [Attestor::
|
58
|
+
# @return [Attestor::Validators]
|
59
59
|
def set(context)
|
60
|
-
|
60
|
+
validators = select { |item| item.used_in_context? context }
|
61
61
|
|
62
|
-
self.class.new(
|
62
|
+
self.class.new(validators)
|
63
63
|
end
|
64
64
|
|
65
65
|
private
|
66
66
|
|
67
67
|
attr_reader :items
|
68
68
|
|
69
|
-
end # class
|
69
|
+
end # class Validators
|
70
70
|
|
71
71
|
end # module Validations
|
72
72
|
|
data/lib/attestor/version.rb
CHANGED
@@ -0,0 +1,109 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe "Base example" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Account = Struct.new(:client, :limit)
|
7
|
+
Transaction = Struct.new(:account, :sum)
|
8
|
+
Transfer = Struct.new(:debet, :credit)
|
9
|
+
|
10
|
+
ConsistencyPolicy = Struct.new(:debet, :credit)
|
11
|
+
LimitPolicy = Struct.new(:transaction)
|
12
|
+
InternalTransfer = Struct.new(:debet, :credit)
|
13
|
+
|
14
|
+
class ConsistencyPolicy
|
15
|
+
include Attestor::Policy
|
16
|
+
|
17
|
+
validate :consistent
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def consistent
|
22
|
+
return if debet.sum + credit.sum == 0
|
23
|
+
invalid :inconsistent
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class LimitPolicy
|
28
|
+
include Attestor::Policy
|
29
|
+
|
30
|
+
validate :limited
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def limited
|
35
|
+
return unless (transaction.account.limit + transaction.sum) < 0
|
36
|
+
invalid :over_the_limit
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class InternalTransfer
|
41
|
+
include Attestor::Policy
|
42
|
+
|
43
|
+
validate :internal
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def internal
|
48
|
+
return if debet.account.client == credit.account.client
|
49
|
+
invalid :external
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Transfer
|
54
|
+
include Attestor::Validations
|
55
|
+
|
56
|
+
follow_policy :consistent
|
57
|
+
follow_policy :limited, except: :blocked
|
58
|
+
follow_policy :internal, only: :blocked
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def consistent
|
63
|
+
ConsistencyPolicy.new(debet, credit)
|
64
|
+
end
|
65
|
+
|
66
|
+
def limited
|
67
|
+
LimitPolicy.new(debet).or internal
|
68
|
+
end
|
69
|
+
|
70
|
+
def internal
|
71
|
+
InternalTransfer.new(debet, credit)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:alice) { Account.new("Alice", 100) }
|
77
|
+
let(:bob) { Account.new("Bob", 100) }
|
78
|
+
|
79
|
+
let(:a_to_a) do
|
80
|
+
Transfer.new Transaction.new(alice, -150), Transaction.new(alice, 150)
|
81
|
+
end
|
82
|
+
|
83
|
+
let(:a_to_b) do
|
84
|
+
Transfer.new Transaction.new(alice, -150), Transaction.new(bob, 150)
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:b_to_a) do
|
88
|
+
Transfer.new Transaction.new(bob, -50), Transaction.new(alice, 50)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "works fine" do
|
92
|
+
expect { a_to_a.validate }.not_to raise_error
|
93
|
+
expect { b_to_a.validate }.not_to raise_error
|
94
|
+
|
95
|
+
expect { a_to_b.validate }.to raise_error Attestor::InvalidError
|
96
|
+
expect { b_to_a.validate :blocked }.to raise_error Attestor::InvalidError
|
97
|
+
end
|
98
|
+
|
99
|
+
after do
|
100
|
+
%w(
|
101
|
+
Transfer
|
102
|
+
InternalTransfer
|
103
|
+
LimitPolicy
|
104
|
+
ConsistencyPolicy
|
105
|
+
Transaction
|
106
|
+
Account
|
107
|
+
).each { |klass| Object.send :remove_const, klass }
|
108
|
+
end
|
109
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
require "support/policies"
|
4
|
+
|
5
|
+
describe Attestor::Validations::Validator do
|
4
6
|
|
5
7
|
subject { described_class.new "foo" }
|
6
8
|
|
@@ -36,9 +38,13 @@ describe Attestor::Validations::Item do
|
|
36
38
|
.to eq(described_class.new "foo", only: %i(bar))
|
37
39
|
end
|
38
40
|
|
41
|
+
it "accepts array option :policy" do
|
42
|
+
expect { described_class.new "foo", policy: :bar }.not_to raise_error
|
43
|
+
end
|
44
|
+
|
39
45
|
end # describe .new
|
40
46
|
|
41
|
-
describe "
|
47
|
+
describe "#name" do
|
42
48
|
|
43
49
|
it "is initialized as a symbol" do
|
44
50
|
expect(subject.name).to eq :foo
|
@@ -46,11 +52,24 @@ describe Attestor::Validations::Item do
|
|
46
52
|
|
47
53
|
end # describe .name
|
48
54
|
|
55
|
+
describe "#policy?" do
|
56
|
+
|
57
|
+
it "is set to false by default" do
|
58
|
+
expect(subject.policy?).to eq false
|
59
|
+
end
|
60
|
+
|
61
|
+
it "can be initialized" do
|
62
|
+
subject = described_class.new "foo", policy: 1
|
63
|
+
expect(subject.policy?).to eq true
|
64
|
+
end
|
65
|
+
|
66
|
+
end # describe #policy?
|
67
|
+
|
49
68
|
describe "#==" do
|
50
69
|
|
51
70
|
subject { described_class.new :foo, except: [:foo], only: %i(bar) }
|
52
71
|
|
53
|
-
context "item with the same
|
72
|
+
context "item with the same arguments" do
|
54
73
|
|
55
74
|
let(:other) { described_class.new :foo, except: %i(foo), only: %i(bar) }
|
56
75
|
|
@@ -150,4 +169,51 @@ describe Attestor::Validations::Item do
|
|
150
169
|
|
151
170
|
end # describe #name
|
152
171
|
|
172
|
+
describe "#validate" do
|
173
|
+
|
174
|
+
after { subject.validate object }
|
175
|
+
|
176
|
+
context "when a #policy isn't set" do
|
177
|
+
|
178
|
+
let(:object) { double foo: nil }
|
179
|
+
subject { described_class.new :foo }
|
180
|
+
|
181
|
+
it "calls validation method" do
|
182
|
+
expect(object).to receive :foo
|
183
|
+
end
|
184
|
+
|
185
|
+
end # context
|
186
|
+
|
187
|
+
context "when a #policy is set to valid policy" do
|
188
|
+
|
189
|
+
let(:object) { double foo: valid_policy }
|
190
|
+
subject { described_class.new :foo, policy: true }
|
191
|
+
|
192
|
+
it "calls policy method" do
|
193
|
+
expect(object).to receive(:foo)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "doesn't call #invalid" do
|
197
|
+
expect(object).not_to receive(:invalid)
|
198
|
+
end
|
199
|
+
|
200
|
+
end # context
|
201
|
+
|
202
|
+
context "when a #policy is set to valid policy" do
|
203
|
+
|
204
|
+
let(:object) { double foo: invalid_policy, invalid: nil }
|
205
|
+
subject { described_class.new :foo, policy: true }
|
206
|
+
|
207
|
+
it "calls policy method" do
|
208
|
+
expect(object).to receive(:foo)
|
209
|
+
end
|
210
|
+
|
211
|
+
it "calls #invalid with name" do
|
212
|
+
expect(object).to receive(:invalid).with(:foo)
|
213
|
+
end
|
214
|
+
|
215
|
+
end # context
|
216
|
+
|
217
|
+
end # describe #validate
|
218
|
+
|
153
219
|
end # describe Attestor::Validation
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
describe Attestor::Validations::
|
3
|
+
describe Attestor::Validations::Validators do
|
4
4
|
|
5
|
-
let(:
|
5
|
+
let(:validator_class) { Attestor::Validations::Validator }
|
6
6
|
|
7
7
|
describe ".new" do
|
8
8
|
|
@@ -22,11 +22,11 @@ describe Attestor::Validations::Collection do
|
|
22
22
|
expect(subject.each).to be_kind_of Enumerator
|
23
23
|
end
|
24
24
|
|
25
|
-
it "iterates trough
|
26
|
-
|
27
|
-
subject = described_class.new
|
25
|
+
it "iterates trough validators" do
|
26
|
+
validators = %w(foo bar foo).map(&validator_class.method(:new))
|
27
|
+
subject = described_class.new validators
|
28
28
|
|
29
|
-
expect(subject.to_a).to match_array
|
29
|
+
expect(subject.to_a).to match_array validators
|
30
30
|
end
|
31
31
|
|
32
32
|
end # describe #each
|
@@ -37,17 +37,17 @@ describe Attestor::Validations::Collection do
|
|
37
37
|
|
38
38
|
let(:result) { subject.add("foo") }
|
39
39
|
|
40
|
-
it "returns
|
40
|
+
it "returns validators" do
|
41
41
|
expect(result).to be_kind_of described_class
|
42
42
|
end
|
43
43
|
|
44
|
-
it "adds item to
|
44
|
+
it "adds item to validators" do
|
45
45
|
item = result.first
|
46
|
-
expect(item).to eq :foo
|
46
|
+
expect(item.name).to eq :foo
|
47
47
|
end
|
48
48
|
|
49
49
|
it "preserves existing items" do
|
50
|
-
expect(result.add(:bar).
|
50
|
+
expect(result.add(:bar).map(&:name)).to contain_exactly :foo, :bar
|
51
51
|
end
|
52
52
|
|
53
53
|
end # context
|
@@ -56,15 +56,15 @@ describe Attestor::Validations::Collection do
|
|
56
56
|
|
57
57
|
let(:result) { subject.add "foo", only: [:foo] }
|
58
58
|
|
59
|
-
it "adds item to
|
60
|
-
expect(result.
|
61
|
-
expect(result.set(:foo).
|
62
|
-
expect(result.set(:all).
|
59
|
+
it "adds item to validators" do
|
60
|
+
expect(result.map(&:name)).to eq [:foo]
|
61
|
+
expect(result.set(:foo).map(&:name)).to eq [:foo]
|
62
|
+
expect(result.set(:all).map(&:name)).to eq []
|
63
63
|
end
|
64
64
|
|
65
65
|
end # context
|
66
66
|
|
67
|
-
context "existing
|
67
|
+
context "existing validator" do
|
68
68
|
|
69
69
|
subject { described_class.new.add "foo" }
|
70
70
|
|
@@ -90,11 +90,11 @@ describe Attestor::Validations::Collection do
|
|
90
90
|
end
|
91
91
|
|
92
92
|
it "returns a set of items used in given context" do
|
93
|
-
expect(subject.set("cad").
|
94
|
-
expect(subject.set("cam").
|
95
|
-
expect(subject.set("all").
|
93
|
+
expect(subject.set("cad").map(&:name)).to contain_exactly :foo, :baz
|
94
|
+
expect(subject.set("cam").map(&:name)).to contain_exactly :foo, :bar
|
95
|
+
expect(subject.set("all").map(&:name)).to contain_exactly :bar, :baz
|
96
96
|
end
|
97
97
|
|
98
98
|
end # describe #context
|
99
99
|
|
100
|
-
end # describe Attestor::
|
100
|
+
end # describe Attestor::Validators
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require "support/policies" # for #valid_policy and #invalid_policy definitions
|
4
|
+
|
3
5
|
describe Attestor::Validations do
|
4
6
|
|
5
|
-
let(:
|
7
|
+
let(:validators_class) { Attestor::Validations::Validators }
|
8
|
+
let(:validator_class) { Attestor::Validations::Validator }
|
9
|
+
let(:collection_class) { Attestor::Validations::Validators }
|
6
10
|
let(:item_class) { Attestor::Validations::Item }
|
7
11
|
let(:message_class) { Attestor::Validations::Message }
|
8
12
|
let(:invalid_error) { Attestor::InvalidError }
|
@@ -13,17 +17,17 @@ describe Attestor::Validations do
|
|
13
17
|
|
14
18
|
subject { test_class.new }
|
15
19
|
|
16
|
-
describe ".
|
20
|
+
describe ".validators" do
|
17
21
|
|
18
|
-
it "returns
|
19
|
-
expect(test_class.
|
22
|
+
it "returns Validators" do
|
23
|
+
expect(test_class.validators).to be_kind_of validators_class
|
20
24
|
end
|
21
25
|
|
22
26
|
it "is empty by default" do
|
23
|
-
expect(test_class.
|
27
|
+
expect(test_class.validators.to_a).to be_empty
|
24
28
|
end
|
25
29
|
|
26
|
-
end # describe .
|
30
|
+
end # describe .validators
|
27
31
|
|
28
32
|
describe ".validate" do
|
29
33
|
|
@@ -31,58 +35,59 @@ describe Attestor::Validations do
|
|
31
35
|
|
32
36
|
before { test_class.validate :foo }
|
33
37
|
|
34
|
-
it "
|
35
|
-
expect(test_class.
|
38
|
+
it "registers a validator" do
|
39
|
+
expect(test_class.validators.map(&:name)).to eq [:foo]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "sets a validator's policy to false" do
|
43
|
+
expect(test_class.validators.first).not_to be_policy
|
36
44
|
end
|
37
45
|
|
38
46
|
end # context
|
39
47
|
|
40
|
-
context "with
|
48
|
+
context "with restrictions" do
|
41
49
|
|
42
50
|
before { test_class.validate :foo, only: %w(bar baz), except: "bar" }
|
43
51
|
|
44
52
|
it "uses options" do
|
45
|
-
expect(test_class.
|
46
|
-
expect(test_class.
|
47
|
-
expect(test_class.
|
53
|
+
expect(test_class.validators.map(&:name)).to eq [:foo]
|
54
|
+
expect(test_class.validators.set(:baz).map(&:name)).to eq [:foo]
|
55
|
+
expect(test_class.validators.set(:bar).map(&:name)).to eq []
|
48
56
|
end
|
49
57
|
|
50
58
|
end # context
|
51
59
|
|
52
60
|
end # describe .validate
|
53
61
|
|
54
|
-
describe "
|
62
|
+
describe ".follow_policy" do
|
55
63
|
|
56
|
-
|
57
|
-
test_class.validate :foo
|
58
|
-
test_class.validate :bar, only: :all
|
59
|
-
test_class.validate :baz, only: :foo
|
60
|
-
%i(foo bar baz).each { |method| allow(subject).to receive(method) }
|
61
|
-
end
|
64
|
+
context "without options" do
|
62
65
|
|
63
|
-
|
66
|
+
before { test_class.follow_policy :foo }
|
64
67
|
|
65
|
-
it "
|
66
|
-
expect(
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
it "registers a validator" do
|
69
|
+
expect(test_class.validators.map(&:name)).to eq [:foo]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "sets a validator's policy to true" do
|
73
|
+
expect(test_class.validators.first).to be_policy
|
70
74
|
end
|
71
75
|
|
72
76
|
end # context
|
73
77
|
|
74
|
-
context "
|
78
|
+
context "with restrictions" do
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
expect(
|
80
|
-
|
80
|
+
before { test_class.follow_policy :foo, only: %w(bar baz), except: "bar" }
|
81
|
+
|
82
|
+
it "uses options" do
|
83
|
+
expect(test_class.validators.map(&:name)).to eq [:foo]
|
84
|
+
expect(test_class.validators.set(:baz).map(&:name)).to eq [:foo]
|
85
|
+
expect(test_class.validators.set(:bar).map(&:name)).to eq []
|
81
86
|
end
|
82
87
|
|
83
88
|
end # context
|
84
89
|
|
85
|
-
end # describe
|
90
|
+
end # describe .follow_policy
|
86
91
|
|
87
92
|
describe "#invalid" do
|
88
93
|
|
@@ -123,4 +128,40 @@ describe Attestor::Validations do
|
|
123
128
|
|
124
129
|
end # invalid
|
125
130
|
|
131
|
+
describe "#validate" do
|
132
|
+
|
133
|
+
before do
|
134
|
+
test_class.validate :foo
|
135
|
+
test_class.validate :bar, only: :all
|
136
|
+
test_class.follow_policy :baz, only: :foo
|
137
|
+
|
138
|
+
allow(subject).to receive(:foo)
|
139
|
+
allow(subject).to receive(:bar)
|
140
|
+
allow(subject).to receive(:baz) { valid_policy }
|
141
|
+
end
|
142
|
+
|
143
|
+
context "without an argument" do
|
144
|
+
|
145
|
+
it "calls validators for :all context" do
|
146
|
+
expect(subject).to receive(:foo)
|
147
|
+
expect(subject).to receive(:bar)
|
148
|
+
expect(subject).not_to receive(:baz)
|
149
|
+
subject.validate
|
150
|
+
end
|
151
|
+
|
152
|
+
end # context
|
153
|
+
|
154
|
+
context ":foo" do
|
155
|
+
|
156
|
+
it "calls validators for :foo context" do
|
157
|
+
expect(subject).to receive(:foo)
|
158
|
+
expect(subject).to receive(:baz)
|
159
|
+
expect(subject).not_to receive(:bar)
|
160
|
+
subject.validate :foo
|
161
|
+
end
|
162
|
+
|
163
|
+
end # context
|
164
|
+
|
165
|
+
end # describe #validate
|
166
|
+
|
126
167
|
end # describe Attestor::Validations
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attestor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kozin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: extlib
|
@@ -81,10 +81,11 @@ files:
|
|
81
81
|
- lib/attestor/policy/or.rb
|
82
82
|
- lib/attestor/policy/xor.rb
|
83
83
|
- lib/attestor/validations.rb
|
84
|
-
- lib/attestor/validations/collection.rb
|
85
|
-
- lib/attestor/validations/item.rb
|
86
84
|
- lib/attestor/validations/message.rb
|
85
|
+
- lib/attestor/validations/validator.rb
|
86
|
+
- lib/attestor/validations/validators.rb
|
87
87
|
- lib/attestor/version.rb
|
88
|
+
- spec/features/example_spec.rb
|
88
89
|
- spec/spec_helper.rb
|
89
90
|
- spec/support/policies.rb
|
90
91
|
- spec/tests/invalid_error_spec.rb
|
@@ -96,9 +97,9 @@ files:
|
|
96
97
|
- spec/tests/policy/or_spec.rb
|
97
98
|
- spec/tests/policy/xor_spec.rb
|
98
99
|
- spec/tests/policy_spec.rb
|
99
|
-
- spec/tests/validations/collection_spec.rb
|
100
|
-
- spec/tests/validations/item_spec.rb
|
101
100
|
- spec/tests/validations/message_spec.rb
|
101
|
+
- spec/tests/validations/validator_spec.rb
|
102
|
+
- spec/tests/validations/validators_spec.rb
|
102
103
|
- spec/tests/validations_spec.rb
|
103
104
|
homepage: https://github.com/nepalez/attestor
|
104
105
|
licenses:
|
@@ -130,8 +131,8 @@ test_files:
|
|
130
131
|
- spec/tests/invalid_error_spec.rb
|
131
132
|
- spec/tests/validations_spec.rb
|
132
133
|
- spec/tests/validations/message_spec.rb
|
133
|
-
- spec/tests/validations/
|
134
|
-
- spec/tests/validations/
|
134
|
+
- spec/tests/validations/validator_spec.rb
|
135
|
+
- spec/tests/validations/validators_spec.rb
|
135
136
|
- spec/tests/policy/or_spec.rb
|
136
137
|
- spec/tests/policy/factory_spec.rb
|
137
138
|
- spec/tests/policy/and_spec.rb
|
@@ -139,5 +140,6 @@ test_files:
|
|
139
140
|
- spec/tests/policy/negator_spec.rb
|
140
141
|
- spec/tests/policy/not_spec.rb
|
141
142
|
- spec/tests/policy/node_spec.rb
|
143
|
+
- spec/features/example_spec.rb
|
142
144
|
- spec/support/policies.rb
|
143
145
|
has_rdoc:
|