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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30d3fe02039b7df2f24461bd4eb4387deda2e93c
4
- data.tar.gz: f1ec4731ded236180c094171dff71d10fad448db
3
+ metadata.gz: e5d5fbaf26c97f5c5dd838289b64904714d118b7
4
+ data.tar.gz: 777f4abaa5ef17cfe19627cbb69b9e960394a4c7
5
5
  SHA512:
6
- metadata.gz: 595eaa79f730fdf98e8119e5d98c30f0711278ada88dde24b87dc3ea770a8206fef0d0a2c899714504299b530f4eeb93898f1666fb2d61601e01954a76ed6436
7
- data.tar.gz: 47078bc23acc2749312d84e9b343ae9236b300b099bafbb7a4554a5c4b34a7c0f7579716597ccdd736d7b3e05edc2956f7b8ad7d353cb226aafbe31e844933d2
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
- ```ruby
94
+ ```yaml
99
95
  # config/locales/en.yml
96
+ ---
100
97
  en:
101
98
  attestor:
102
- validations:
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
- Adding Contexts
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
- Just as the `:except` option blacklists validations, the `:only` method whitelists them:
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 < Struct.new(:debet, :credit)
149
- include Attestor::Validations
150
-
145
+ class Transfer
146
+ # ...
151
147
  validate :consistent, only: :fair_trade
152
- end
148
+ validate :consistent, only: :legal
153
149
 
154
- fraud_transfer.validate # => PASSES
155
- fraud_transfer.validate :fair_trade # => InvalidError
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
- From the other hand, the target needs to know nothing about how the policy works with data:
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
- validate :constistent
185
+ follow_policy :consistency, only: :fair_trade
189
186
 
190
187
  private
191
188
 
192
- def consistent
193
- policy = ConsistencyPolicy.new(debet.sum, credit.sum)
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
- The "new" method `valid?` just returns true or false, trowing error messages out as unsignificant details.
195
+ In case the policy object is invalid, validation raises an exception.
200
196
 
201
- If you need messages from policy, you can use `validate` method and capture its exception. But should you?! Instead you'd better to provie the message, that makes sense in the Transfer context.
197
+ The name of the method (`consistency`) will be used for the error message:
202
198
 
203
- [Policy Object design pattern]: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
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 we isolated policies, we can provide complex policies from simpler ones.
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 `Policy` factory methods to provide compositions:
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, called without argument(s), don't provide a policy object. They return lazy composer, expecting `#not` method.
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 invalid_policy
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 invalid_policy
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 invalid_policy
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/item"
11
- require_relative "attestor/validations/collection"
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"
@@ -5,14 +5,14 @@ module Attestor
5
5
  # API for objects to be validated
6
6
  module Validations
7
7
 
8
- # Calls all validations used in the selected context
8
+ # Calls all validators for given context
9
9
  #
10
- # @raise [Attestor::Validations::InvalidError] if validations fail
11
- # @raise [NoMethodError] if some of validations are not implemented
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.validations.set(context).each(&method(:__send__))
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 items describing applied validations
45
+ # Returns a collection of applied validators
46
46
  #
47
- # @return [Attestor::Collection]
47
+ # @return [Attestor::Validators]
48
48
  #
49
49
  # @api private
50
- def validations
51
- @validations ||= Collection.new
50
+ def validators
51
+ @validators ||= Validators.new
52
52
  end
53
53
 
54
- # Adds an item to {#validations}
54
+ # Registers a validator
55
55
  #
56
- # Mutates the class by changing its {#validations} attribute!
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::Collection] the updated collection
65
+ # @return [Attestor::Validators] the updated collection
66
66
  def validate(name, options = {})
67
- @validations = validations.add(name, options)
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
- # Describes an item of validations' collection
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 Item
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::Collection::Item]
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 Item
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 Collection
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::Collection::Item>] items
17
+ # @param [Array<Attestor::Validators::Validator>] items
18
18
  #
19
- # @return [Attestor::Collection]
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::Collection::Item] item
30
+ # @yieldparam [Attestor::Validators::Validator] item
31
31
  # items from the collection
32
32
  #
33
33
  # @return [Enumerator]
34
34
  def each
35
- return to_enum unless block_given?
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 the collection, updated with new item
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::Collection]
46
+ # @return [Attestor::Validators]
47
47
  def add(name, options = {})
48
- item = Item.new(name, options)
49
- return self if items.include? item
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 the collection of items used in given context
54
+ # Returns validators used in given context
55
55
  #
56
56
  # @param [#to_sym] context
57
57
  #
58
- # @return [Attestor::Collection]
58
+ # @return [Attestor::Validators]
59
59
  def set(context)
60
- collection = items.select { |item| item.used_in_context? context }
60
+ validators = select { |item| item.used_in_context? context }
61
61
 
62
- self.class.new(collection)
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 Collection
69
+ end # class Validators
70
70
 
71
71
  end # module Validations
72
72
 
@@ -4,6 +4,6 @@ module Attestor
4
4
 
5
5
  # The semantic version of the module.
6
6
  # @see http://semver.org/ Semantic versioning 2.0
7
- VERSION = "0.0.1".freeze
7
+ VERSION = "0.1.0".freeze
8
8
 
9
9
  end # module Attestor
@@ -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
- describe Attestor::Validations::Item do
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 ".name" do
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 name and options" do
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::Collection do
3
+ describe Attestor::Validations::Validators do
4
4
 
5
- let(:item_class) { Attestor::Validations::Item }
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 the unique item names" do
26
- items = %w(foo bar foo).map(&item_class.method(:new))
27
- subject = described_class.new items
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 %i(foo bar)
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 the collection" do
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 the collection" do
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).to_a).to contain_exactly :foo, :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 the collection" do
60
- expect(result.to_a).to eq [:foo]
61
- expect(result.set(:foo).to_a).to eq [:foo]
62
- expect(result.set(:all).to_a).to eq []
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 item" do
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").to_a).to contain_exactly :foo, :baz
94
- expect(subject.set("cam").to_a).to contain_exactly :foo, :bar
95
- expect(subject.set("all").to_a).to contain_exactly :bar, :baz
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::Collection
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(:collection_class) { Attestor::Validations::Collection }
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 ".validations" do
20
+ describe ".validators" do
17
21
 
18
- it "returns a Collection" do
19
- expect(test_class.validations).to be_kind_of collection_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.validations.to_a).to be_empty
27
+ expect(test_class.validators.to_a).to be_empty
24
28
  end
25
29
 
26
- end # describe .validations
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 "adds the item to the collection" do
35
- expect(test_class.validations.to_a).to eq [:foo]
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 options" do
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.validations.to_a).to eq [:foo]
46
- expect(test_class.validations.set(:baz).to_a).to eq [:foo]
47
- expect(test_class.validations.set(:bar).to_a).to eq []
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 "#validate" do
62
+ describe ".follow_policy" do
55
63
 
56
- before do
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
- context "without an argument" do
66
+ before { test_class.follow_policy :foo }
64
67
 
65
- it "calls validations for :all context" do
66
- expect(subject).to receive(:foo)
67
- expect(subject).to receive(:bar)
68
- expect(subject).not_to receive(:baz)
69
- subject.validate
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 ":foo" do
78
+ context "with restrictions" do
75
79
 
76
- it "calls validations for :foo context" do
77
- expect(subject).to receive(:foo)
78
- expect(subject).to receive(:baz)
79
- expect(subject).not_to receive(:bar)
80
- subject.validate :foo
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 #validate
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.1
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-25 00:00:00.000000000 Z
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/collection_spec.rb
134
- - spec/tests/validations/item_spec.rb
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: