attestor 0.0.1 → 0.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/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:
|