attestor 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/README.md +69 -55
  4. data/config/metrics/rubocop.yml +4 -0
  5. data/lib/attestor/invalid_error.rb +1 -12
  6. data/lib/attestor/policy/and.rb +2 -19
  7. data/lib/attestor/policy/negator.rb +2 -29
  8. data/lib/attestor/policy/node.rb +7 -33
  9. data/lib/attestor/policy/not.rb +2 -27
  10. data/lib/attestor/policy/or.rb +2 -19
  11. data/lib/attestor/policy/xor.rb +2 -19
  12. data/lib/attestor/policy.rb +0 -18
  13. data/lib/attestor/report.rb +51 -0
  14. data/lib/attestor/validations/delegator.rb +3 -18
  15. data/lib/attestor/validations/message.rb +1 -16
  16. data/lib/attestor/validations/reporter.rb +21 -0
  17. data/lib/attestor/validations/validator.rb +4 -63
  18. data/lib/attestor/validations/validators.rb +13 -41
  19. data/lib/attestor/validations.rb +12 -1
  20. data/lib/attestor/version.rb +1 -1
  21. data/lib/attestor.rb +2 -0
  22. data/spec/features/example_spec.rb +5 -5
  23. data/spec/support/policies.rb +5 -5
  24. data/spec/tests/policy/and_spec.rb +2 -2
  25. data/spec/tests/policy/node_spec.rb +9 -9
  26. data/spec/tests/policy/not_spec.rb +2 -2
  27. data/spec/tests/policy/or_spec.rb +2 -2
  28. data/spec/tests/policy/xor_spec.rb +2 -2
  29. data/spec/tests/policy_spec.rb +0 -48
  30. data/spec/tests/report_spec.rb +106 -0
  31. data/spec/tests/validations/delegator_spec.rb +6 -6
  32. data/spec/tests/validations/message_spec.rb +1 -1
  33. data/spec/tests/validations/reporter_spec.rb +47 -0
  34. data/spec/tests/validations/validator_spec.rb +56 -74
  35. data/spec/tests/validations/validators_spec.rb +81 -4
  36. data/spec/tests/validations_spec.rb +98 -10
  37. metadata +9 -3
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ module Attestor
4
+
5
+ module Validations
6
+
7
+ # @private
8
+ module Reporter
9
+
10
+ def validate(object)
11
+ validate! object
12
+ Report.new(object)
13
+ rescue InvalidError => error
14
+ Report.new(error.object, error)
15
+ end
16
+
17
+ end # module Reporter
18
+
19
+ end # module Validations
20
+
21
+ end # module Attestor
@@ -4,86 +4,31 @@ module Attestor
4
4
 
5
5
  module Validations
6
6
 
7
- # Describe a validator for class instances
8
- #
9
- # @example
10
- # validator = Validator.new(:foo, only: :baz)
11
- #
12
- # validator.used_in_context? :baz # => true
13
- # validator.validate object
14
- #
15
- # @api private
7
+ # @private
16
8
  class Validator
9
+ include Reporter
17
10
 
18
- # @!scope class
19
- # @!method new(name, except: [], only: [])
20
- # Creates a named item with blacklist or whitelist of contexts
21
- #
22
- # @param [#to_sym] name
23
- # @option [#to_sym, Array<#to_sym>] :except
24
- # @option [#to_sym, Array<#to_sym>] :only
25
- #
26
- # @return [Attestor::Validations::Validator]
27
-
28
- # @private
29
11
  def initialize(name = :invalid, except: nil, only: nil, &block)
30
12
  @name = name.to_sym
31
13
  @whitelist = normalize(only)
32
14
  @blacklist = normalize(except)
33
15
  @block = block
34
- generate_id
35
16
  freeze
36
17
  end
37
18
 
38
- # @!attribute [r] name
39
- # The name of the item
40
- #
41
- # @return [Symbol]
42
- attr_reader :name
43
-
44
- # Compares an item to another one
45
- #
46
- # @param [Object] other
47
- #
48
- # @return [Boolean]
49
- def ==(other)
50
- other.instance_of?(self.class) ? id.equal?(other.id) : false
51
- end
19
+ attr_reader :name, :whitelist, :blacklist, :block
52
20
 
53
- # Checks if the item should be used in given context
54
- #
55
- # @param [#to_sym] context
56
- #
57
- # @return [Boolean]
58
21
  def used_in_context?(context)
59
22
  symbol = context.to_sym
60
23
  whitelisted?(symbol) && !blacklisted?(symbol)
61
24
  end
62
25
 
63
- # Validates given object
64
- #
65
- # @param [Object] object
66
- #
67
- # @raise [Attestor::InvalidError]
68
- # if object doesn't match validation rule
69
- #
70
- # @return [undefined]
71
- def validate(object)
26
+ def validate!(object)
72
27
  block ? object.instance_eval(&block) : object.__send__(name)
73
28
  end
74
29
 
75
- protected
76
-
77
- # @!attribute [r] id
78
- # The item's identity
79
- #
80
- # @return [String]
81
- attr_reader :id
82
-
83
30
  private
84
31
 
85
- attr_reader :whitelist, :blacklist, :block
86
-
87
32
  def whitelisted?(symbol)
88
33
  whitelist.empty? || whitelist.include?(symbol)
89
34
  end
@@ -92,10 +37,6 @@ module Attestor
92
37
  blacklist.include? symbol
93
38
  end
94
39
 
95
- def generate_id
96
- @id = [name, whitelist, blacklist].hash
97
- end
98
-
99
40
  def normalize(list)
100
41
  Array(list).map(&:to_sym).uniq
101
42
  end
@@ -4,72 +4,44 @@ module Attestor
4
4
 
5
5
  module Validations
6
6
 
7
- # The collection of validations used by class instances
8
- #
9
- # @api private
7
+ # @private
10
8
  class Validators
11
9
  include Enumerable
10
+ include Reporter
12
11
 
13
- # @!scope class
14
- # @!method new(items = [])
15
- # Creates an immutable collection with optional list of items
16
- #
17
- # @param [Array<Attestor::Validators::Validator>] items
18
- #
19
- # @return [Attestor::Validators]
20
-
21
- # @private
22
12
  def initialize(*items)
23
13
  @items = items.flatten
24
14
  freeze
25
15
  end
26
16
 
27
- # Iterates through the collection
28
- #
29
- # @yield the block
30
- # @yieldparam [Attestor::Validators::Validator] item
31
- # items from the collection
32
- #
33
- # @return [Enumerator]
34
17
  def each
35
18
  block_given? ? items.each { |item| yield(item) } : to_enum
36
19
  end
37
20
 
38
- # Returns validators used in given context
39
- #
40
- # @param [#to_sym] context
41
- #
42
- # @return [Attestor::Validators]
43
21
  def set(context)
44
- validators = select { |item| item.used_in_context? context }
45
-
46
- self.class.new(validators)
22
+ self.class.new select { |item| item.used_in_context? context }
47
23
  end
48
24
 
49
- # Returns validators updated by a new validator with given args
50
- #
51
- # @param [Array] args
52
- #
53
- # @return [Attestor::Validators]
54
25
  def add_validator(*args, &block)
55
- add_item Validator, *args, &block
26
+ self.class.new items, Validator.new(*args, &block)
56
27
  end
57
28
 
58
- # Returns validators updated by a new validator with given args
59
- #
60
- # @param [Array] args
61
- #
62
- # @return [Attestor::Validators]
63
29
  def add_delegator(*args, &block)
64
- add_item Delegator, *args, &block
30
+ self.class.new items, Delegator.new(*args, &block)
31
+ end
32
+
33
+ def validate!(object)
34
+ results = errors(object)
35
+ return if results.empty?
36
+ fail InvalidError.new object, results.map(&:messages).flatten
65
37
  end
66
38
 
67
39
  private
68
40
 
69
41
  attr_reader :items
70
42
 
71
- def add_item(type, *args, &block)
72
- self.class.new items, type.new(*args, &block)
43
+ def errors(object)
44
+ map { |validator| validator.validate(object) }.select(&:invalid?)
73
45
  end
74
46
 
75
47
  end # class Validators
@@ -7,12 +7,23 @@ module Attestor
7
7
 
8
8
  # Calls all validators for given context
9
9
  #
10
+ # @param [#to_sym] context
11
+ #
10
12
  # @raise [Attestor::Validations::InvalidError] if validators fail
11
13
  # @raise [NoMethodError] if some of validators are not implemented
12
14
  #
13
15
  # @return [undefined]
16
+ def validate!(context = :all)
17
+ self.class.validators.set(context).validate! self
18
+ end
19
+
20
+ # Calls all validators for given context and return validation results
21
+ #
22
+ # @param (see #validate!)
23
+ #
24
+ # @return [undefined]
14
25
  def validate(context = :all)
15
- self.class.validators.set(context).each { |item| item.validate(self) }
26
+ self.class.validators.set(context).validate self
16
27
  end
17
28
 
18
29
  # Raises InvalidError with a corresponding message
@@ -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 = "1.0.0".freeze
7
+ VERSION = "2.0.0".freeze
8
8
 
9
9
  end # module Attestor
data/lib/attestor.rb CHANGED
@@ -5,8 +5,10 @@ require "extlib"
5
5
  require_relative "attestor/version"
6
6
 
7
7
  require_relative "attestor/invalid_error"
8
+ require_relative "attestor/report"
8
9
 
9
10
  require_relative "attestor/validations"
11
+ require_relative "attestor/validations/reporter"
10
12
  require_relative "attestor/validations/validator"
11
13
  require_relative "attestor/validations/delegator"
12
14
  require_relative "attestor/validations/validators"
@@ -56,7 +56,7 @@ describe "Base example" do
56
56
  validates { ConsistencyPolicy.new(debet, credit) }
57
57
  validates :limited, except: :blocked
58
58
  validate only: :blocked do
59
- internal.validate
59
+ internal.validate!
60
60
  end
61
61
 
62
62
  private
@@ -87,11 +87,11 @@ describe "Base example" do
87
87
  end
88
88
 
89
89
  it "works fine" do
90
- expect { a_to_a.validate }.not_to raise_error
91
- expect { b_to_a.validate }.not_to raise_error
90
+ expect { a_to_a.validate! }.not_to raise_error
91
+ expect { b_to_a.validate! }.not_to raise_error
92
92
 
93
- expect { a_to_b.validate }.to raise_error Attestor::InvalidError
94
- expect { b_to_a.validate :blocked }.to raise_error Attestor::InvalidError
93
+ expect { a_to_b.validate! }.to raise_error Attestor::InvalidError
94
+ expect { b_to_a.validate! :blocked }.to raise_error Attestor::InvalidError
95
95
  end
96
96
 
97
97
  after do
@@ -3,11 +3,11 @@
3
3
  # Definitions for testing compound policies
4
4
 
5
5
  def valid_policy
6
- double valid?: true, invalid?: false, validate: nil
6
+ double validate!: nil, validate: double(valid?: true, invalid?: false)
7
7
  end
8
8
 
9
9
  def invalid_policy
10
- double valid?: false, invalid?: true, validate: nil
10
+ double validate!: nil, validate: double(valid?: false, invalid?: true)
11
11
  end
12
12
 
13
13
  shared_examples "creating a node" do
@@ -27,12 +27,12 @@ end # shared examples
27
27
  shared_examples "failing validation" do
28
28
 
29
29
  it "[raises exception]" do
30
- expect { subject.validate }.to raise_error Attestor::InvalidError
30
+ expect { subject.validate! }.to raise_error Attestor::InvalidError
31
31
  end
32
32
 
33
33
  it "[adds itself to exception]" do
34
34
  begin
35
- subject.validate
35
+ subject.validate!
36
36
  rescue => error
37
37
  expect(error.object).to eq subject
38
38
  end
@@ -43,7 +43,7 @@ end # shared examples
43
43
  shared_examples "passing validation" do
44
44
 
45
45
  it "[raises exception]" do
46
- expect { subject.validate }.not_to raise_error
46
+ expect { subject.validate! }.not_to raise_error
47
47
  end
48
48
 
49
49
  end # shared examples
@@ -17,7 +17,7 @@ describe Attestor::Policy::And do
17
17
 
18
18
  end # context
19
19
 
20
- describe "#validate" do
20
+ describe "#validate!" do
21
21
 
22
22
  context "when all the parts are valid" do
23
23
 
@@ -35,6 +35,6 @@ describe Attestor::Policy::And do
35
35
 
36
36
  end # context
37
37
 
38
- end # describe #validate
38
+ end # describe #validate!
39
39
 
40
40
  end # describe Policy::Base::Not
@@ -2,13 +2,13 @@
2
2
 
3
3
  describe Attestor::Policy::Node do
4
4
 
5
- let(:policy_class) { Attestor::Policy }
5
+ let(:policy_module) { Attestor::Policy }
6
6
  let(:invalid_error) { Attestor::InvalidError }
7
7
 
8
8
  describe ".new" do
9
9
 
10
10
  it "creates a policy" do
11
- expect(subject).to be_kind_of policy_class
11
+ expect(subject).to be_kind_of policy_module
12
12
  end
13
13
 
14
14
  it "creates a collection" do
@@ -39,35 +39,35 @@ describe Attestor::Policy::Node do
39
39
 
40
40
  describe "#each" do
41
41
 
42
- let(:branches) { 3.times.map { double } }
42
+ let(:branches) { 3.times.map { |n| double validate: n } }
43
43
 
44
44
  it "returns an enumerator" do
45
45
  expect(subject.each).to be_kind_of Enumerator
46
46
  end
47
47
 
48
- it "iterates through brances" do
48
+ it "iterates through branches' validation reports" do
49
49
  subject = described_class.new(branches)
50
- expect(subject.to_a).to eq branches
50
+ expect(subject.to_a).to eq [0, 1, 2]
51
51
  end
52
52
 
53
53
  end # each
54
54
 
55
- describe "#validate" do
55
+ describe "#validate!" do
56
56
 
57
57
  let(:message) { Attestor::Validations::Message.new :base, subject }
58
58
 
59
59
  it "raises InvalidError" do
60
- expect { subject.validate }.to raise_error invalid_error
60
+ expect { subject.validate! }.to raise_error invalid_error
61
61
  end
62
62
 
63
63
  it "adds the :invalid message" do
64
64
  begin
65
- subject.validate
65
+ subject.validate!
66
66
  rescue => error
67
67
  expect(error.messages).to contain_exactly message
68
68
  end
69
69
  end
70
70
 
71
- end # describe #validate
71
+ end # describe #validate!
72
72
 
73
73
  end # describe Attestor::Policy::Node
@@ -17,7 +17,7 @@ describe Attestor::Policy::Not do
17
17
 
18
18
  end # context
19
19
 
20
- describe "#validate" do
20
+ describe "#validate!" do
21
21
 
22
22
  context "when a part is invalid" do
23
23
 
@@ -35,6 +35,6 @@ describe Attestor::Policy::Not do
35
35
 
36
36
  end # context
37
37
 
38
- end # describe #validate
38
+ end # describe #validate!
39
39
 
40
40
  end # describe Policy::Base::Not
@@ -17,7 +17,7 @@ describe Attestor::Policy::Or do
17
17
 
18
18
  end # context
19
19
 
20
- describe "#validate" do
20
+ describe "#validate!" do
21
21
 
22
22
  context "when valid part exists" do
23
23
 
@@ -35,6 +35,6 @@ describe Attestor::Policy::Or do
35
35
 
36
36
  end # context
37
37
 
38
- end # describe #validate
38
+ end # describe #validate!
39
39
 
40
40
  end # describe Policy::Base::Not
@@ -17,7 +17,7 @@ describe Attestor::Policy::Xor do
17
17
 
18
18
  end # context
19
19
 
20
- describe "#validate" do
20
+ describe "#validate!" do
21
21
 
22
22
  context "when both valid and invalid parts exist" do
23
23
 
@@ -43,6 +43,6 @@ describe Attestor::Policy::Xor do
43
43
 
44
44
  end # context
45
45
 
46
- end # describe #validate
46
+ end # describe #validate!
47
47
 
48
48
  end # describe Policy::Base::Not
@@ -51,54 +51,6 @@ describe Attestor::Policy do
51
51
 
52
52
  end # describe .new
53
53
 
54
- describe "#valid?" do
55
-
56
- context "when #validate method fails" do
57
-
58
- before { allow(subject).to receive(:validate) { fail invalid } }
59
-
60
- it "returns false" do
61
- expect(subject.valid?).to eq false
62
- end
63
-
64
- end
65
-
66
- context "when #validate method passes" do
67
-
68
- before { allow(subject).to receive(:validate) { nil } }
69
-
70
- it "returns true" do
71
- expect(subject.valid?).to eq true
72
- end
73
-
74
- end
75
-
76
- end # describe #valid?
77
-
78
- describe "#invalid?" do
79
-
80
- context "when #validate method fails" do
81
-
82
- before { allow(subject).to receive(:validate) { fail invalid } }
83
-
84
- it "returns true" do
85
- expect(subject.invalid?).to eq true
86
- end
87
-
88
- end
89
-
90
- context "when #validate method passes" do
91
-
92
- before { allow(subject).to receive(:validate) { nil } }
93
-
94
- it "returns false" do
95
- expect(subject.invalid?).to eq false
96
- end
97
-
98
- end
99
-
100
- end # describe #invalid?
101
-
102
54
  describe "#and" do
103
55
 
104
56
  it "calls .and class factory method with self" do
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ describe Attestor::Report do
4
+
5
+ let(:invalid_error) { Attestor::InvalidError }
6
+
7
+ let(:messages) { ["foo"] }
8
+ let(:object) { double }
9
+ let(:error) { invalid_error.new object, messages }
10
+ subject { described_class.new object, error }
11
+
12
+ describe ".new" do
13
+
14
+ it "creates an immutable object" do
15
+ expect(subject).to be_frozen
16
+ end
17
+
18
+ end # describe .new
19
+
20
+ describe "#object" do
21
+
22
+ it "is initialized" do
23
+ expect(subject.object).to eq object
24
+ end
25
+
26
+ end # describe #object
27
+
28
+ describe "#error" do
29
+
30
+ it "is initialized" do
31
+ expect(subject.error).to eq error
32
+ end
33
+
34
+ it "is set to nil by default" do
35
+ expect(described_class.new(object).error).to be_nil
36
+ end
37
+
38
+ end # describe #error
39
+
40
+ describe "#valid?" do
41
+
42
+ context "when the #error is set" do
43
+
44
+ it "returns false" do
45
+ expect(subject.valid?).to eq false
46
+ end
47
+
48
+ end # context
49
+
50
+ context "when the #error is not set" do
51
+
52
+ subject { described_class.new object }
53
+
54
+ it "returns true" do
55
+ expect(subject.valid?).to eq true
56
+ end
57
+
58
+ end # context
59
+
60
+ end # describe #valid?
61
+
62
+ describe "#invalid?" do
63
+
64
+ context "when the #error is set" do
65
+
66
+ it "returns true" do
67
+ expect(subject.invalid?).to eq true
68
+ end
69
+
70
+ end # context
71
+
72
+ context "when the #error is not set" do
73
+
74
+ subject { described_class.new object }
75
+
76
+ it "returns false" do
77
+ expect(subject.invalid?).to eq false
78
+ end
79
+
80
+ end # context
81
+
82
+ end # describe #invalid?
83
+
84
+ describe "#messages" do
85
+
86
+ context "when the #error is set" do
87
+
88
+ it "returns error's messages" do
89
+ expect(subject.messages).to eq messages
90
+ end
91
+
92
+ end # context
93
+
94
+ context "when the #error is not set" do
95
+
96
+ subject { described_class.new object }
97
+
98
+ it "returns an empty array" do
99
+ expect(subject.messages).to eq []
100
+ end
101
+
102
+ end # context
103
+
104
+ end # describe #messages
105
+
106
+ end # describe Attestor::Report
@@ -13,17 +13,17 @@ describe Attestor::Validations::Delegator do
13
13
 
14
14
  end # describe .new
15
15
 
16
- describe "#validate" do
16
+ describe "#validate!" do
17
17
 
18
18
  let(:object) { double foo: valid_policy }
19
19
 
20
20
  context "when initialized without a block" do
21
21
 
22
22
  subject { described_class.new "foo" }
23
- after { subject.validate object }
23
+ after { subject.validate! object }
24
24
 
25
25
  it "delegates validation to named method" do
26
- expect(object).to receive_message_chain(:foo, :validate)
26
+ expect(object).to receive_message_chain(:foo, :validate!)
27
27
  end
28
28
 
29
29
  end # context
@@ -31,14 +31,14 @@ describe Attestor::Validations::Delegator do
31
31
  context "when initialized with a block" do
32
32
 
33
33
  subject { described_class.new { foo } }
34
- after { subject.validate object }
34
+ after { subject.validate! object }
35
35
 
36
36
  it "delegates validation to block" do
37
- expect(object).to receive_message_chain(:foo, :validate)
37
+ expect(object).to receive_message_chain(:foo, :validate!)
38
38
  end
39
39
 
40
40
  end # context
41
41
 
42
- end # describe #validate
42
+ end # describe #validate!
43
43
 
44
44
  end # describe Attestor::Validations::Delegator
@@ -18,7 +18,7 @@ describe Attestor::Validations::Message do
18
18
 
19
19
  describe ".new" do
20
20
 
21
- context "with a symbol argument" do
21
+ context "with a symbolic argument" do
22
22
 
23
23
  subject { described_class.new :invalid, scope, foo: "bar" }
24
24