attestor 1.0.0 → 2.0.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.
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