activemodel 4.2.0 → 6.1.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.
- 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 }
|