activemodel 4.2.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +49 -37
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -22
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute_assignment.rb +55 -0
- data/lib/active_model/attribute_methods.rb +150 -73
- data/lib/active_model/attribute_mutation_tracker.rb +181 -0
- data/lib/active_model/attribute_set/builder.rb +191 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attributes.rb +132 -0
- data/lib/active_model/callbacks.rb +31 -25
- data/lib/active_model/conversion.rb +20 -9
- data/lib/active_model/dirty.rb +142 -116
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +436 -202
- data/lib/active_model/forbidden_attributes_protection.rb +6 -3
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +47 -42
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +7 -7
- data/lib/active_model/naming.rb +36 -18
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +8 -0
- data/lib/active_model/secure_password.rb +61 -67
- data/lib/active_model/serialization.rb +48 -17
- data/lib/active_model/serializers/json.rb +22 -13
- data/lib/active_model/translation.rb +5 -4
- data/lib/active_model/type/big_integer.rb +14 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +46 -0
- data/lib/active_model/type/date.rb +52 -0
- data/lib/active_model/type/date_time.rb +46 -0
- data/lib/active_model/type/decimal.rb +69 -0
- data/lib/active_model/type/float.rb +35 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +48 -0
- data/lib/active_model/type/helpers/time_value.rb +90 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +35 -0
- data/lib/active_model/type/integer.rb +67 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +35 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/value.rb +133 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/validations/absence.rb +6 -4
- data/lib/active_model/validations/acceptance.rb +72 -14
- data/lib/active_model/validations/callbacks.rb +23 -19
- data/lib/active_model/validations/clusivity.rb +18 -12
- data/lib/active_model/validations/confirmation.rb +27 -14
- data/lib/active_model/validations/exclusion.rb +7 -4
- data/lib/active_model/validations/format.rb +27 -27
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +8 -7
- data/lib/active_model/validations/length.rb +35 -32
- data/lib/active_model/validations/numericality.rb +72 -34
- data/lib/active_model/validations/presence.rb +3 -3
- data/lib/active_model/validations/validates.rb +17 -15
- data/lib/active_model/validations/with.rb +6 -12
- data/lib/active_model/validations.rb +58 -23
- data/lib/active_model/validator.rb +23 -17
- data/lib/active_model/version.rb +4 -2
- data/lib/active_model.rb +18 -11
- metadata +44 -25
- data/lib/active_model/serializers/xml.rb +0 -238
- data/lib/active_model/test_case.rb +0 -4
@@ -1,12 +1,12 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module ActiveModel
|
4
4
|
module Validations
|
5
5
|
class LengthValidator < EachValidator # :nodoc:
|
6
6
|
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
|
7
7
|
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
|
8
8
|
|
9
|
-
RESERVED_OPTIONS
|
9
|
+
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long]
|
10
10
|
|
11
11
|
def initialize(options)
|
12
12
|
if range = (options.delete(:in) || options.delete(:within))
|
@@ -25,20 +25,19 @@ module ActiveModel
|
|
25
25
|
keys = CHECKS.keys & options.keys
|
26
26
|
|
27
27
|
if keys.empty?
|
28
|
-
raise ArgumentError,
|
28
|
+
raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option."
|
29
29
|
end
|
30
30
|
|
31
31
|
keys.each do |key|
|
32
32
|
value = options[key]
|
33
33
|
|
34
|
-
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
|
35
|
-
raise ArgumentError, ":#{key} must be a
|
34
|
+
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) || value.is_a?(Proc)
|
35
|
+
raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc"
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
40
|
def validate_each(record, attribute, value)
|
41
|
-
value = tokenize(value)
|
42
41
|
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
|
43
42
|
errors_options = options.except(*RESERVED_OPTIONS)
|
44
43
|
|
@@ -46,7 +45,13 @@ module ActiveModel
|
|
46
45
|
next unless check_value = options[key]
|
47
46
|
|
48
47
|
if !value.nil? || skip_nil_check?(key)
|
49
|
-
|
48
|
+
case check_value
|
49
|
+
when Proc
|
50
|
+
check_value = check_value.call(record)
|
51
|
+
when Symbol
|
52
|
+
check_value = record.send(check_value)
|
53
|
+
end
|
54
|
+
next if value_length.public_send(validity_check, check_value)
|
50
55
|
end
|
51
56
|
|
52
57
|
errors_options[:count] = check_value
|
@@ -54,27 +59,20 @@ module ActiveModel
|
|
54
59
|
default_message = options[MESSAGES[key]]
|
55
60
|
errors_options[:message] ||= default_message if default_message
|
56
61
|
|
57
|
-
record.errors.add(attribute, MESSAGES[key], errors_options)
|
62
|
+
record.errors.add(attribute, MESSAGES[key], **errors_options)
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
61
66
|
private
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
options[:tokenizer].call(value)
|
66
|
-
end || value
|
67
|
-
end
|
68
|
-
|
69
|
-
def skip_nil_check?(key)
|
70
|
-
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
|
71
|
-
end
|
67
|
+
def skip_nil_check?(key)
|
68
|
+
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
|
69
|
+
end
|
72
70
|
end
|
73
71
|
|
74
72
|
module HelperMethods
|
75
|
-
|
76
|
-
#
|
77
|
-
#
|
73
|
+
# Validates that the specified attributes match the length restrictions
|
74
|
+
# supplied. Only one constraint option can be used at a time apart from
|
75
|
+
# +:minimum+ and +:maximum+ that can be combined together:
|
78
76
|
#
|
79
77
|
# class Person < ActiveRecord::Base
|
80
78
|
# validates_length_of :first_name, maximum: 30
|
@@ -84,38 +82,43 @@ module ActiveModel
|
|
84
82
|
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
|
85
83
|
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
|
86
84
|
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
|
87
|
-
# validates_length_of :
|
88
|
-
#
|
85
|
+
# validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.'
|
86
|
+
#
|
87
|
+
# private
|
88
|
+
#
|
89
|
+
# def words_in_essay
|
90
|
+
# essay.scan(/\w+/)
|
91
|
+
# end
|
89
92
|
# end
|
90
93
|
#
|
91
|
-
#
|
94
|
+
# Constraint options:
|
95
|
+
#
|
92
96
|
# * <tt>:minimum</tt> - The minimum size of the attribute.
|
93
97
|
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
|
94
|
-
# default if not used with
|
98
|
+
# default if not used with +:minimum+.
|
95
99
|
# * <tt>:is</tt> - The exact size of the attribute.
|
96
100
|
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
|
97
101
|
# the attribute.
|
98
102
|
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
|
103
|
+
#
|
104
|
+
# Other options:
|
105
|
+
#
|
99
106
|
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
100
107
|
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
101
108
|
# * <tt>:too_long</tt> - The error message if the attribute goes over the
|
102
109
|
# maximum (default is: "is too long (maximum is %{count} characters)").
|
103
110
|
# * <tt>:too_short</tt> - The error message if the attribute goes under the
|
104
|
-
# minimum (default is: "is too short (
|
111
|
+
# minimum (default is: "is too short (minimum is %{count} characters)").
|
105
112
|
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
|
106
113
|
# method and the attribute is the wrong size (default is: "is the wrong
|
107
114
|
# length (should be %{count} characters)").
|
108
115
|
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
|
109
116
|
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
|
110
117
|
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
|
111
|
-
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
|
112
|
-
# (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
|
113
|
-
# as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
|
114
|
-
# which counts individual characters.
|
115
118
|
#
|
116
119
|
# There is also a list of default options supported by every validator:
|
117
120
|
# +:if+, +:unless+, +:on+ and +:strict+.
|
118
|
-
# See <tt>ActiveModel::
|
121
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
119
122
|
def validates_length_of(*attr_names)
|
120
123
|
validates_with LengthValidator, _merge_attributes(attr_names)
|
121
124
|
end
|
@@ -1,5 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bigdecimal/util"
|
2
4
|
|
5
|
+
module ActiveModel
|
3
6
|
module Validations
|
4
7
|
class NumericalityValidator < EachValidator # :nodoc:
|
5
8
|
CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
|
@@ -8,6 +11,10 @@ module ActiveModel
|
|
8
11
|
|
9
12
|
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
|
10
13
|
|
14
|
+
INTEGER_REGEX = /\A[+-]?\d+\z/
|
15
|
+
|
16
|
+
HEXADECIMAL_REGEX = /\A[+-]?0[xX]/
|
17
|
+
|
11
18
|
def check_validity!
|
12
19
|
keys = CHECKS.keys - [:odd, :even]
|
13
20
|
options.slice(*keys).each do |option, value|
|
@@ -17,35 +24,24 @@ module ActiveModel
|
|
17
24
|
end
|
18
25
|
end
|
19
26
|
|
20
|
-
def validate_each(record, attr_name, value)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
raw_value ||= value
|
25
|
-
|
26
|
-
if record_attribute_changed_in_place?(record, attr_name)
|
27
|
-
raw_value = value
|
27
|
+
def validate_each(record, attr_name, value, precision: Float::DIG, scale: nil)
|
28
|
+
unless is_number?(value, precision, scale)
|
29
|
+
record.errors.add(attr_name, :not_a_number, **filtered_options(value))
|
30
|
+
return
|
28
31
|
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
unless value = parse_raw_value_as_a_number(raw_value)
|
33
|
-
record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
|
33
|
+
if allow_only_integer?(record) && !is_integer?(value)
|
34
|
+
record.errors.add(attr_name, :not_an_integer, **filtered_options(value))
|
34
35
|
return
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
-
unless value = parse_raw_value_as_an_integer(raw_value)
|
39
|
-
record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
|
40
|
-
return
|
41
|
-
end
|
42
|
-
end
|
38
|
+
value = parse_as_number(value, precision, scale)
|
43
39
|
|
44
40
|
options.slice(*CHECKS.keys).each do |option, option_value|
|
45
41
|
case option
|
46
42
|
when :odd, :even
|
47
|
-
unless value.to_i.
|
48
|
-
record.errors.add(attr_name, option, filtered_options(value))
|
43
|
+
unless value.to_i.public_send(CHECKS[option])
|
44
|
+
record.errors.add(attr_name, option, **filtered_options(value))
|
49
45
|
end
|
50
46
|
else
|
51
47
|
case option_value
|
@@ -55,23 +51,44 @@ module ActiveModel
|
|
55
51
|
option_value = record.send(option_value)
|
56
52
|
end
|
57
53
|
|
58
|
-
|
59
|
-
|
54
|
+
option_value = parse_as_number(option_value, precision, scale)
|
55
|
+
|
56
|
+
unless value.public_send(CHECKS[option], option_value)
|
57
|
+
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
60
58
|
end
|
61
59
|
end
|
62
60
|
end
|
63
61
|
end
|
64
62
|
|
65
|
-
|
63
|
+
private
|
64
|
+
def parse_as_number(raw_value, precision, scale)
|
65
|
+
if raw_value.is_a?(Float)
|
66
|
+
parse_float(raw_value, precision, scale)
|
67
|
+
elsif raw_value.is_a?(Numeric)
|
68
|
+
raw_value
|
69
|
+
elsif is_integer?(raw_value)
|
70
|
+
raw_value.to_i
|
71
|
+
elsif !is_hexadecimal_literal?(raw_value)
|
72
|
+
parse_float(Kernel.Float(raw_value), precision, scale)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_float(raw_value, precision, scale)
|
77
|
+
(scale ? raw_value.truncate(scale) : raw_value).to_d(precision)
|
78
|
+
end
|
66
79
|
|
67
|
-
def
|
68
|
-
|
80
|
+
def is_number?(raw_value, precision, scale)
|
81
|
+
!parse_as_number(raw_value, precision, scale).nil?
|
69
82
|
rescue ArgumentError, TypeError
|
70
|
-
|
83
|
+
false
|
71
84
|
end
|
72
85
|
|
73
|
-
def
|
74
|
-
|
86
|
+
def is_integer?(raw_value)
|
87
|
+
INTEGER_REGEX.match?(raw_value.to_s)
|
88
|
+
end
|
89
|
+
|
90
|
+
def is_hexadecimal_literal?(raw_value)
|
91
|
+
HEXADECIMAL_REGEX.match?(raw_value.to_s)
|
75
92
|
end
|
76
93
|
|
77
94
|
def filtered_options(value)
|
@@ -91,7 +108,26 @@ module ActiveModel
|
|
91
108
|
end
|
92
109
|
end
|
93
110
|
|
94
|
-
|
111
|
+
def prepare_value_for_validation(value, record, attr_name)
|
112
|
+
return value if record_attribute_changed_in_place?(record, attr_name)
|
113
|
+
|
114
|
+
came_from_user = :"#{attr_name}_came_from_user?"
|
115
|
+
|
116
|
+
if record.respond_to?(came_from_user)
|
117
|
+
if record.public_send(came_from_user)
|
118
|
+
raw_value = record.public_send(:"#{attr_name}_before_type_cast")
|
119
|
+
elsif record.respond_to?(:read_attribute)
|
120
|
+
raw_value = record.read_attribute(attr_name)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
before_type_cast = :"#{attr_name}_before_type_cast"
|
124
|
+
if record.respond_to?(before_type_cast)
|
125
|
+
raw_value = record.public_send(before_type_cast)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
raw_value || value
|
130
|
+
end
|
95
131
|
|
96
132
|
def record_attribute_changed_in_place?(record, attr_name)
|
97
133
|
record.respond_to?(:attribute_changed_in_place?) &&
|
@@ -102,8 +138,9 @@ module ActiveModel
|
|
102
138
|
module HelperMethods
|
103
139
|
# Validates whether the value of the specified attribute is numeric by
|
104
140
|
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
|
105
|
-
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\
|
106
|
-
# (if <tt>only_integer</tt> is set to +true+).
|
141
|
+
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
|
142
|
+
# (if <tt>only_integer</tt> is set to +true+). Precision of Kernel.Float values
|
143
|
+
# are guaranteed up to 15 digits.
|
107
144
|
#
|
108
145
|
# class Person < ActiveRecord::Base
|
109
146
|
# validates_numericality_of :value, on: :create
|
@@ -114,7 +151,7 @@ module ActiveModel
|
|
114
151
|
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
|
115
152
|
# integer, e.g. an integral value (default is +false+).
|
116
153
|
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
|
117
|
-
# +false+). Notice that for
|
154
|
+
# +false+). Notice that for Integer and Float columns empty strings are
|
118
155
|
# converted to +nil+.
|
119
156
|
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
|
120
157
|
# supplied value.
|
@@ -133,7 +170,7 @@ module ActiveModel
|
|
133
170
|
#
|
134
171
|
# There is also a list of default options supported by every validator:
|
135
172
|
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
136
|
-
# See <tt>ActiveModel::
|
173
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
137
174
|
#
|
138
175
|
# The following checks can also be supplied with a proc or a symbol which
|
139
176
|
# corresponds to a method:
|
@@ -144,6 +181,7 @@ module ActiveModel
|
|
144
181
|
# * <tt>:less_than</tt>
|
145
182
|
# * <tt>:less_than_or_equal_to</tt>
|
146
183
|
# * <tt>:only_integer</tt>
|
184
|
+
# * <tt>:other_than</tt>
|
147
185
|
#
|
148
186
|
# For example:
|
149
187
|
#
|
@@ -1,10 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module ActiveModel
|
3
|
-
|
4
4
|
module Validations
|
5
5
|
class PresenceValidator < EachValidator # :nodoc:
|
6
6
|
def validate_each(record, attr_name, value)
|
7
|
-
record.errors.add(attr_name, :blank, options) if value.blank?
|
7
|
+
record.errors.add(attr_name, :blank, **options) if value.blank?
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
@@ -30,7 +30,7 @@ module ActiveModel
|
|
30
30
|
#
|
31
31
|
# There is also a list of default options supported by every validator:
|
32
32
|
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
33
|
-
# See <tt>ActiveModel::
|
33
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
34
34
|
def validates_presence_of(*attr_names)
|
35
35
|
validates_with PresenceValidator, _merge_attributes(attr_names)
|
36
36
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/slice"
|
2
4
|
|
3
5
|
module ActiveModel
|
4
6
|
module Validations
|
@@ -10,6 +12,7 @@ module ActiveModel
|
|
10
12
|
#
|
11
13
|
# Examples of using the default rails validators:
|
12
14
|
#
|
15
|
+
# validates :username, absence: true
|
13
16
|
# validates :terms, acceptance: true
|
14
17
|
# validates :password, confirmation: true
|
15
18
|
# validates :username, exclusion: { in: %w(admin superuser) }
|
@@ -18,7 +21,6 @@ module ActiveModel
|
|
18
21
|
# validates :first_name, length: { maximum: 30 }
|
19
22
|
# validates :age, numericality: true
|
20
23
|
# validates :username, presence: true
|
21
|
-
# validates :username, uniqueness: true
|
22
24
|
#
|
23
25
|
# The power of the +validates+ method comes when using custom validators
|
24
26
|
# and default validators in one call for a given attribute.
|
@@ -26,7 +28,7 @@ module ActiveModel
|
|
26
28
|
# class EmailValidator < ActiveModel::EachValidator
|
27
29
|
# def validate_each(record, attribute, value)
|
28
30
|
# record.errors.add attribute, (options[:message] || "is not an email") unless
|
29
|
-
#
|
31
|
+
# /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.match?(value)
|
30
32
|
# end
|
31
33
|
# end
|
32
34
|
#
|
@@ -34,7 +36,7 @@ module ActiveModel
|
|
34
36
|
# include ActiveModel::Validations
|
35
37
|
# attr_accessor :name, :email
|
36
38
|
#
|
37
|
-
# validates :name, presence: true,
|
39
|
+
# validates :name, presence: true, length: { maximum: 100 }
|
38
40
|
# validates :email, presence: true, email: true
|
39
41
|
# end
|
40
42
|
#
|
@@ -46,7 +48,7 @@ module ActiveModel
|
|
46
48
|
#
|
47
49
|
# class TitleValidator < ActiveModel::EachValidator
|
48
50
|
# def validate_each(record, attribute, value)
|
49
|
-
# record.errors.add attribute, "must start with 'the'" unless
|
51
|
+
# record.errors.add attribute, "must start with 'the'" unless /\Athe/i.match?(value)
|
50
52
|
# end
|
51
53
|
# end
|
52
54
|
#
|
@@ -62,7 +64,7 @@ module ActiveModel
|
|
62
64
|
# and strings in shortcut form.
|
63
65
|
#
|
64
66
|
# validates :email, format: /@/
|
65
|
-
# validates :
|
67
|
+
# validates :role, inclusion: %w(admin contributor)
|
66
68
|
# validates :password, length: 6..20
|
67
69
|
#
|
68
70
|
# When using shortcut form, ranges and arrays are passed to your
|
@@ -72,7 +74,7 @@ module ActiveModel
|
|
72
74
|
# There is also a list of options that could be used along with validators:
|
73
75
|
#
|
74
76
|
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
75
|
-
# Runs in all validation contexts by default
|
77
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
76
78
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
77
79
|
# <tt>on: :custom_validation_context</tt> or
|
78
80
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
@@ -94,7 +96,7 @@ module ActiveModel
|
|
94
96
|
# Example:
|
95
97
|
#
|
96
98
|
# validates :password, presence: true, confirmation: true, if: :password_required?
|
97
|
-
# validates :token,
|
99
|
+
# validates :token, length: 24, strict: TokenLengthException
|
98
100
|
#
|
99
101
|
#
|
100
102
|
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
|
@@ -111,15 +113,16 @@ module ActiveModel
|
|
111
113
|
defaults[:attributes] = attributes
|
112
114
|
|
113
115
|
validations.each do |key, options|
|
114
|
-
next unless options
|
115
116
|
key = "#{key.to_s.camelize}Validator"
|
116
117
|
|
117
118
|
begin
|
118
|
-
validator = key.include?(
|
119
|
+
validator = key.include?("::") ? key.constantize : const_get(key)
|
119
120
|
rescue NameError
|
120
121
|
raise ArgumentError, "Unknown validator: '#{key}'"
|
121
122
|
end
|
122
123
|
|
124
|
+
next unless options
|
125
|
+
|
123
126
|
validates_with(validator, defaults.merge(_parse_validates_options(options)))
|
124
127
|
end
|
125
128
|
end
|
@@ -148,15 +151,14 @@ module ActiveModel
|
|
148
151
|
validates(*(attributes << options))
|
149
152
|
end
|
150
153
|
|
151
|
-
|
152
|
-
|
154
|
+
private
|
153
155
|
# When creating custom validators, it might be useful to be able to specify
|
154
156
|
# additional default keys. This can be done by overwriting this method.
|
155
|
-
def _validates_default_keys
|
156
|
-
[:if, :unless, :on, :allow_blank, :allow_nil
|
157
|
+
def _validates_default_keys
|
158
|
+
[:if, :unless, :on, :allow_blank, :allow_nil, :strict]
|
157
159
|
end
|
158
160
|
|
159
|
-
def _parse_validates_options(options)
|
161
|
+
def _parse_validates_options(options)
|
160
162
|
case options
|
161
163
|
when TrueClass
|
162
164
|
{}
|
@@ -1,15 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
|
1
5
|
module ActiveModel
|
2
6
|
module Validations
|
3
|
-
module HelperMethods
|
4
|
-
private
|
5
|
-
def _merge_attributes(attr_names)
|
6
|
-
options = attr_names.extract_options!.symbolize_keys
|
7
|
-
attr_names.flatten!
|
8
|
-
options[:attributes] = attr_names
|
9
|
-
options
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
7
|
class WithValidator < EachValidator # :nodoc:
|
14
8
|
def validate_each(record, attr, val)
|
15
9
|
method_name = options[:with]
|
@@ -53,7 +47,7 @@ module ActiveModel
|
|
53
47
|
#
|
54
48
|
# Configuration options:
|
55
49
|
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
56
|
-
# Runs in all validation contexts by default
|
50
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
57
51
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
58
52
|
# <tt>on: :custom_validation_context</tt> or
|
59
53
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
@@ -69,7 +63,7 @@ module ActiveModel
|
|
69
63
|
# The method, proc or string should return or evaluate to a +true+ or
|
70
64
|
# +false+ value.
|
71
65
|
# * <tt>:strict</tt> - Specifies whether validation should be strict.
|
72
|
-
# See <tt>ActiveModel::
|
66
|
+
# See <tt>ActiveModel::Validations#validates!</tt> for more information.
|
73
67
|
#
|
74
68
|
# If you pass any additional configuration options, they will be passed
|
75
69
|
# to the class and available as +options+:
|
@@ -1,9 +1,8 @@
|
|
1
|
-
|
2
|
-
require 'active_support/core_ext/hash/keys'
|
3
|
-
require 'active_support/core_ext/hash/except'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
6
4
|
|
5
|
+
module ActiveModel
|
7
6
|
# == Active \Model \Validations
|
8
7
|
#
|
9
8
|
# Provides a full validation framework to your objects.
|
@@ -16,7 +15,7 @@ module ActiveModel
|
|
16
15
|
# attr_accessor :first_name, :last_name
|
17
16
|
#
|
18
17
|
# validates_each :first_name, :last_name do |record, attr, value|
|
19
|
-
# record.errors.add attr,
|
18
|
+
# record.errors.add attr, "starts with z." if value.start_with?("z")
|
20
19
|
# end
|
21
20
|
# end
|
22
21
|
#
|
@@ -47,10 +46,10 @@ module ActiveModel
|
|
47
46
|
include HelperMethods
|
48
47
|
|
49
48
|
attr_accessor :validation_context
|
49
|
+
private :validation_context=
|
50
50
|
define_callbacks :validate, scope: :name
|
51
51
|
|
52
|
-
class_attribute :_validators
|
53
|
-
self._validators = Hash.new { |h,k| h[k] = [] }
|
52
|
+
class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] }
|
54
53
|
end
|
55
54
|
|
56
55
|
module ClassMethods
|
@@ -62,13 +61,13 @@ module ActiveModel
|
|
62
61
|
# attr_accessor :first_name, :last_name
|
63
62
|
#
|
64
63
|
# validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
|
65
|
-
# record.errors.add attr,
|
64
|
+
# record.errors.add attr, "starts with z." if value.start_with?("z")
|
66
65
|
# end
|
67
66
|
# end
|
68
67
|
#
|
69
68
|
# Options:
|
70
69
|
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
71
|
-
# Runs in all validation contexts by default
|
70
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
72
71
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
73
72
|
# <tt>on: :custom_validation_context</tt> or
|
74
73
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
@@ -87,7 +86,7 @@ module ActiveModel
|
|
87
86
|
validates_with BlockValidator, _merge_attributes(attr_names), &block
|
88
87
|
end
|
89
88
|
|
90
|
-
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze
|
89
|
+
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
|
91
90
|
|
92
91
|
# Adds a validation method or block to the class. This is useful when
|
93
92
|
# overriding the +validate+ instance method becomes too unwieldy and
|
@@ -129,9 +128,12 @@ module ActiveModel
|
|
129
128
|
# end
|
130
129
|
# end
|
131
130
|
#
|
131
|
+
# Note that the return value of validation methods is not relevant.
|
132
|
+
# It's not possible to halt the validate callback chain.
|
133
|
+
#
|
132
134
|
# Options:
|
133
135
|
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
134
|
-
# Runs in all validation contexts by default
|
136
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
135
137
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
136
138
|
# <tt>on: :custom_validation_context</tt> or
|
137
139
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
@@ -144,6 +146,9 @@ module ActiveModel
|
|
144
146
|
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
145
147
|
# method, proc or string should return or evaluate to a +true+ or +false+
|
146
148
|
# value.
|
149
|
+
#
|
150
|
+
# NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
|
151
|
+
#
|
147
152
|
def validate(*args, &block)
|
148
153
|
options = args.extract_options!
|
149
154
|
|
@@ -157,14 +162,14 @@ module ActiveModel
|
|
157
162
|
|
158
163
|
if options.key?(:on)
|
159
164
|
options = options.dup
|
160
|
-
options[:
|
161
|
-
options[:if]
|
162
|
-
|
163
|
-
|
165
|
+
options[:on] = Array(options[:on])
|
166
|
+
options[:if] = [
|
167
|
+
->(o) { !(options[:on] & Array(o.validation_context)).empty? },
|
168
|
+
*options[:if]
|
169
|
+
]
|
164
170
|
end
|
165
171
|
|
166
|
-
args
|
167
|
-
set_callback(:validate, *args, &block)
|
172
|
+
set_callback(:validate, *args, options, &block)
|
168
173
|
end
|
169
174
|
|
170
175
|
# List all validators that are being used to validate the model using
|
@@ -300,8 +305,6 @@ module ActiveModel
|
|
300
305
|
# Runs all the specified validations and returns +true+ if no errors were
|
301
306
|
# added otherwise +false+.
|
302
307
|
#
|
303
|
-
# Aliased as validate.
|
304
|
-
#
|
305
308
|
# class Person
|
306
309
|
# include ActiveModel::Validations
|
307
310
|
#
|
@@ -371,6 +374,15 @@ module ActiveModel
|
|
371
374
|
!valid?(context)
|
372
375
|
end
|
373
376
|
|
377
|
+
# Runs all the validations within the specified context. Returns +true+ if
|
378
|
+
# no errors are found, raises +ValidationError+ otherwise.
|
379
|
+
#
|
380
|
+
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
|
381
|
+
# some <tt>:on</tt> option will only run in the specified context.
|
382
|
+
def validate!(context = nil)
|
383
|
+
valid?(context) || raise_validation_error
|
384
|
+
end
|
385
|
+
|
374
386
|
# Hook method defining how an attribute value should be retrieved. By default
|
375
387
|
# this is assumed to be an instance named after the attribute. Override this
|
376
388
|
# method in subclasses should you need to retrieve the value for a given
|
@@ -389,13 +401,36 @@ module ActiveModel
|
|
389
401
|
# end
|
390
402
|
alias :read_attribute_for_validation :send
|
391
403
|
|
392
|
-
|
393
|
-
|
394
|
-
def run_validations! #:nodoc:
|
404
|
+
private
|
405
|
+
def run_validations!
|
395
406
|
_run_validate_callbacks
|
396
407
|
errors.empty?
|
397
408
|
end
|
409
|
+
|
410
|
+
def raise_validation_error # :doc:
|
411
|
+
raise(ValidationError.new(self))
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# = Active Model ValidationError
|
416
|
+
#
|
417
|
+
# Raised by <tt>validate!</tt> when the model is invalid. Use the
|
418
|
+
# +model+ method to retrieve the record which did not validate.
|
419
|
+
#
|
420
|
+
# begin
|
421
|
+
# complex_operation_that_internally_calls_validate!
|
422
|
+
# rescue ActiveModel::ValidationError => invalid
|
423
|
+
# puts invalid.model.errors
|
424
|
+
# end
|
425
|
+
class ValidationError < StandardError
|
426
|
+
attr_reader :model
|
427
|
+
|
428
|
+
def initialize(model)
|
429
|
+
@model = model
|
430
|
+
errors = @model.errors.full_messages.join(", ")
|
431
|
+
super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
|
432
|
+
end
|
398
433
|
end
|
399
434
|
end
|
400
435
|
|
401
|
-
Dir[File.
|
436
|
+
Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
|