hanami-validations 0.0.0 → 0.5.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.
@@ -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