hanami-validations 0.0.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ module Hanami
2
+ module Validations
3
+ # @since 0.2.2
4
+ # @api private
5
+ class BlankValueChecker
6
+ # @since 0.2.2
7
+ # @api private
8
+ BLANK_STRING_MATCHER = /\A[[:space:]]*\z/.freeze
9
+
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ # Checks if the value is "blank".
15
+ #
16
+ # @since 0.2.2
17
+ # @api private
18
+ def blank_value?
19
+ _nil_value? || _blank_string? || _empty_value?
20
+ end
21
+
22
+ private
23
+
24
+ # Checks if the value is `nil`.
25
+ #
26
+ # @since 0.2.2
27
+ # @api private
28
+ def _nil_value?
29
+ @value.nil?
30
+ end
31
+
32
+ # @since 0.2.2
33
+ # @api private
34
+ def _blank_string?
35
+ (@value.respond_to?(:match) and @value.match(BLANK_STRING_MATCHER))
36
+ end
37
+
38
+ # @since 0.2.2
39
+ # @api private
40
+ def _empty_value?
41
+ return false if _enumerable?
42
+ (@value.respond_to?(:empty?) and @value.empty?)
43
+ end
44
+
45
+ # Collectable classes should not be considered as blank value
46
+ # even if it's responds _true_ to its own `empty?` method.
47
+ #
48
+ # @since 0.4.0
49
+ # @api private
50
+ def _enumerable?
51
+ @value.respond_to?(:each)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ require 'hanami/utils/kernel'
2
+
3
+ module Hanami
4
+ module Validations
5
+ # Coercions for attribute's values.
6
+ #
7
+ # @since 0.1.0
8
+ # @api private
9
+ module Coercions
10
+ # Coerces the given values with the given type
11
+ #
12
+ # @param coercer [Class] the type
13
+ # @param value [Array] of objects to be coerced
14
+ # @param blk [Proc] an optional block to pass to the custom coercer
15
+ #
16
+ # @return [Object,nil] The result of the coercion, if possible
17
+ #
18
+ # @raise [ArgumentError] if the custom coercer's `#initialize` has a wrong arity.
19
+ #
20
+ # @since 0.1.0
21
+ # @api private
22
+ def self.coerce(coercer, value, &blk)
23
+ if ::Hanami::Utils::Kernel.respond_to?(coercer.to_s)
24
+ ::Hanami::Utils::Kernel.__send__(coercer.to_s, value, &blk) rescue nil
25
+ else
26
+ coercer.new(value, &blk)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,95 @@
1
+ module Hanami
2
+ module Validations
3
+ # A single validation error for an attribute
4
+ #
5
+ # @since 0.1.0
6
+ class Error
7
+ # @since 0.2.4
8
+ # @api private
9
+ NAMESPACE_SEPARATOR = '.'.freeze
10
+
11
+ # The name of the attribute
12
+ #
13
+ # @return [Symbol] the name of the attribute
14
+ #
15
+ # @since 0.2.4
16
+ #
17
+ # @see Hanami::Validations::Error#attribute
18
+ #
19
+ # @example
20
+ # error = Error.new(:name, :presence, true, nil, 'author')
21
+ #
22
+ # error.attribute # => "author.name"
23
+ # error.attribute_name # => "name"
24
+ attr_reader :attribute_name
25
+
26
+ # The name of the validation
27
+ #
28
+ # @return [Symbol] the name of the validation
29
+ #
30
+ # @since 0.1.0
31
+ attr_reader :validation
32
+
33
+ # The expected value
34
+ #
35
+ # @return [Object] the expected value
36
+ #
37
+ # @since 0.1.0
38
+ attr_reader :expected
39
+
40
+ # The actual value
41
+ #
42
+ # @return [Object] the actual value
43
+ #
44
+ # @since 0.1.0
45
+ attr_reader :actual
46
+
47
+ # Returns the namespaced attribute name
48
+ #
49
+ # In cases where the error was pulled up from nested validators,
50
+ # `attribute` will be a namespaced string containing
51
+ # parent attribute names separated by a period.
52
+ #
53
+ # @since 0.1.0
54
+ #
55
+ # @see Hanami::Validations::Error#attribute_name
56
+ #
57
+ # @example
58
+ # error = Error.new(:name, :presence, true, nil, 'author')
59
+ #
60
+ # error.attribute # => "author.name"
61
+ # error.attribute_name # => "name"
62
+ attr_accessor :attribute
63
+
64
+ # Initialize a validation error
65
+ #
66
+ # @param attribute_name [Symbol] the name of the attribute
67
+ # @param validation [Symbol] the name of the validation
68
+ # @param expected [Object] the expected value
69
+ # @param actual [Object] the actual value
70
+ # @param namespace [String] the optional namespace
71
+ #
72
+ # @since 0.1.0
73
+ # @api private
74
+ def initialize(attribute_name, validation, expected, actual, namespace = nil)
75
+ @attribute_name = attribute_name.to_s
76
+ @validation = validation
77
+ @expected = expected
78
+ @actual = actual
79
+ @namespace = namespace
80
+ @attribute = [@namespace, attribute_name].compact.join(NAMESPACE_SEPARATOR)
81
+ end
82
+
83
+ # Check if self equals to `other`
84
+ #
85
+ # @since 0.1.0
86
+ def ==(other)
87
+ other.is_a?(self.class) &&
88
+ other.attribute == attribute &&
89
+ other.validation == validation &&
90
+ other.expected == expected &&
91
+ other.actual == actual
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,155 @@
1
+ require 'hanami/validations/error'
2
+
3
+ module Hanami
4
+ module Validations
5
+ # A set of errors for a validator
6
+ #
7
+ # This is the result of calling `#valid?` on a validator.
8
+ #
9
+ # @see Hanami::Validations::Error
10
+ #
11
+ # @since 0.1.0
12
+ class Errors
13
+ # Initialize the errors
14
+ #
15
+ # @since 0.1.0
16
+ # @api private
17
+ def initialize
18
+ @errors = Hash.new
19
+ end
20
+
21
+ # Check if the set is empty
22
+ #
23
+ # @return [TrueClass,FalseClass] the result of the check
24
+ #
25
+ # @since 0.1.0
26
+ #
27
+ # @see Hanami::Validations::Errors#any?
28
+ def empty?
29
+ @errors.empty?
30
+ end
31
+
32
+ # Check if the set has any entry
33
+ #
34
+ # @return [TrueClass,FalseClass] the result of the check
35
+ #
36
+ # @since 0.2.0
37
+ #
38
+ # @see Hanami::Validations::Errors#empty?
39
+ def any?
40
+ @errors.any?
41
+ end
42
+
43
+ # Returns how many validations have failed
44
+ #
45
+ # @return [Fixnum] the count
46
+ #
47
+ # @since 0.1.0
48
+ def count
49
+ errors.count
50
+ end
51
+
52
+ alias_method :size, :count
53
+
54
+ # Clears the internal state of the errors
55
+ #
56
+ # @since 0.1.0
57
+ # @api private
58
+ def clear
59
+ @errors.clear
60
+ end
61
+
62
+ # Iterate thru the errors and yields the given block
63
+ #
64
+ # @param blk [Proc] the given block
65
+ # @yield [error] a Hanami::Validations::Error
66
+ #
67
+ # @see Hanami::Validations::Error
68
+ #
69
+ # @since 0.1.0
70
+ def each(&blk)
71
+ errors.each(&blk)
72
+ end
73
+
74
+ # Iterate thru the errors, yields the given block and collect the
75
+ # returning value.
76
+ #
77
+ # @param blk [Proc] the given block
78
+ # @yield [error] a Hanami::Validations::Error
79
+ #
80
+ # @see Hanami::Validations::Error
81
+ #
82
+ # @since 0.1.0
83
+ def map(&blk)
84
+ errors.map(&blk)
85
+ end
86
+
87
+ # Add an error to the set
88
+ #
89
+ # @param attribute [Symbol] the name of the attribute
90
+ # @param errors [Array] a collection of errors
91
+ #
92
+ # @since 0.1.0
93
+ # @api private
94
+ #
95
+ # @see Hanami::Validations::Error
96
+ def add(attribute, *errors)
97
+ if errors.any?
98
+ @errors[attribute] ||= []
99
+ @errors[attribute].push(*errors)
100
+ end
101
+ end
102
+
103
+ # Return the errors for the given attribute
104
+ #
105
+ # @param attribute [Symbol] the name of the attribute
106
+ #
107
+ # @since 0.1.0
108
+ def for(attribute)
109
+ @errors.fetch(attribute) { [] }
110
+ end
111
+
112
+ # Check if the current set of errors equals to the one who belongs to
113
+ # `other`.
114
+ #
115
+ # @param other [Object] the other term of comparison
116
+ #
117
+ # @return [TrueClass,FalseClass] the result of comparison
118
+ #
119
+ # @since 0.1.0
120
+ def ==(other)
121
+ other.is_a?(self.class) &&
122
+ other.errors == errors
123
+ end
124
+
125
+ alias_method :eql?, :==
126
+
127
+ # Return a serializable Hash representation of the errors.
128
+ #
129
+ # @return [Hanami::Utils::Hash] the Hash
130
+ #
131
+ # @since 0.2.1
132
+ def to_h
133
+ Utils::Hash.new(@errors).deep_dup
134
+ end
135
+
136
+ # Return a flat collection of errors.
137
+ #
138
+ # @return [Array]
139
+ #
140
+ # @since 0.2.1
141
+ def to_a
142
+ errors.dup
143
+ end
144
+
145
+ protected
146
+ # A flatten set of errors for all the attributes
147
+ #
148
+ # @since 0.1.0
149
+ # @api private
150
+ def errors
151
+ @errors.values.flatten
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,22 @@
1
+ module Hanami
2
+ module Validations
3
+ # @since 0.3.1
4
+ # @api private
5
+ class NestedAttributes
6
+ # @since 0.3.1
7
+ # @api private
8
+ def self.fabricate(&blk)
9
+ dup.tap do |klass|
10
+ klass.class_eval { include Hanami::Validations }
11
+ klass.class_eval(&blk)
12
+ end
13
+ end
14
+
15
+ # @since 0.3.1
16
+ # @api private
17
+ def hanami_nested_attributes?
18
+ true
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,81 @@
1
+ module Hanami
2
+ module Validations
3
+ # A set of validations defined on an object
4
+ #
5
+ # @since 0.2.2
6
+ # @api private
7
+ class ValidationSet
8
+ # Allowed validations
9
+ #
10
+ # @since 0.2.2
11
+ # @api private
12
+ VALIDATIONS = [
13
+ :presence,
14
+ :acceptance,
15
+ :format,
16
+ :inclusion,
17
+ :exclusion,
18
+ :confirmation,
19
+ :size,
20
+ :type,
21
+ :nested
22
+ ].freeze
23
+
24
+ # @since 0.2.2
25
+ # @api private
26
+ def initialize
27
+ @validations = Hash.new {|h,k| h[k] = {} }
28
+ end
29
+
30
+ # @since 0.2.2
31
+ # @api private
32
+ def add(name, options)
33
+ @validations[name.to_sym].merge!(
34
+ validate_options!(name, options)
35
+ )
36
+ end
37
+
38
+ # @since 0.2.2
39
+ # @api private
40
+ def each(&blk)
41
+ @validations.each(&blk)
42
+ end
43
+
44
+ # @since 0.2.2
45
+ # @api private
46
+ def each_key(&blk)
47
+ @validations.each_key(&blk)
48
+ end
49
+
50
+ # @since 0.2.3
51
+ # @api private
52
+ def names
53
+ @validations.keys
54
+ end
55
+
56
+ private
57
+ # Checks at the loading time if the user defined validations are recognized
58
+ #
59
+ # @param name [Symbol] the attribute name
60
+ # @param options [Hash] the set of validations associated with the given attribute
61
+ #
62
+ # @raise [ArgumentError] if at least one of the validations are not
63
+ # recognized
64
+ #
65
+ # @since 0.2.2
66
+ # @api private
67
+ def validate_options!(name, options)
68
+ if (unknown = (options.keys - VALIDATIONS)) && unknown.any?
69
+ raise ArgumentError.new(%(Unknown validation(s): #{ unknown.join ', ' } for "#{ name }" attribute))
70
+ end
71
+
72
+ # FIXME remove
73
+ if options[:confirmation]
74
+ add(:"#{ name }_confirmation", {})
75
+ end
76
+
77
+ options
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,29 @@
1
+ module Hanami
2
+ module Validations
3
+ # Validate given validations and return a set of errors
4
+ #
5
+ # @since 0.2.2
6
+ # @api private
7
+ class Validator
8
+ def initialize(validation_set, attributes, errors)
9
+ @validation_set = validation_set
10
+ @attributes = attributes
11
+ @errors = errors
12
+ end
13
+
14
+ # @since 0.2.2
15
+ # @api private
16
+ def validate
17
+ @errors.clear
18
+ @validation_set.each do |name, validations|
19
+ value = @attributes[name]
20
+ value = @attributes[name.to_s] if value.nil?
21
+
22
+ attribute = Attribute.new(@attributes, name, value, validations, @errors)
23
+ attribute.validate
24
+ end
25
+ @errors
26
+ end
27
+ end
28
+ end
29
+ end