activemodel 6.0.4 → 6.1.4
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 +4 -4
- data/CHANGELOG.md +58 -183
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_model.rb +2 -1
- data/lib/active_model/attribute.rb +18 -17
- data/lib/active_model/attribute_assignment.rb +3 -4
- data/lib/active_model/attribute_methods.rb +74 -38
- data/lib/active_model/attribute_mutation_tracker.rb +8 -5
- data/lib/active_model/attribute_set.rb +18 -16
- data/lib/active_model/attribute_set/builder.rb +80 -13
- data/lib/active_model/attributes.rb +20 -24
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/dirty.rb +17 -4
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +316 -208
- data/lib/active_model/gem_version.rb +1 -1
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +2 -2
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +1 -1
- data/lib/active_model/secure_password.rb +14 -14
- data/lib/active_model/serialization.rb +9 -6
- data/lib/active_model/serializers/json.rb +7 -0
- data/lib/active_model/type/date_time.rb +2 -2
- data/lib/active_model/type/float.rb +2 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
- data/lib/active_model/type/helpers/numeric.rb +8 -3
- data/lib/active_model/type/helpers/time_value.rb +27 -17
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -10
- data/lib/active_model/type/integer.rb +11 -2
- data/lib/active_model/type/registry.rb +5 -0
- data/lib/active_model/type/string.rb +12 -2
- data/lib/active_model/type/value.rb +9 -1
- data/lib/active_model/validations.rb +6 -6
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +15 -15
- data/lib/active_model/validations/clusivity.rb +5 -1
- data/lib/active_model/validations/confirmation.rb +2 -2
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -2
- data/lib/active_model/validations/inclusion.rb +1 -1
- data/lib/active_model/validations/length.rb +2 -2
- data/lib/active_model/validations/numericality.rb +54 -41
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +6 -4
- data/lib/active_model/validator.rb +7 -1
- metadata +9 -7
@@ -11,12 +11,22 @@ module ActiveModel
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def to_immutable_string
|
15
|
+
ImmutableString.new(
|
16
|
+
true: @true,
|
17
|
+
false: @false,
|
18
|
+
limit: limit,
|
19
|
+
precision: precision,
|
20
|
+
scale: scale,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
14
24
|
private
|
15
25
|
def cast_value(value)
|
16
26
|
case value
|
17
27
|
when ::String then ::String.new(value)
|
18
|
-
when true then
|
19
|
-
when false then
|
28
|
+
when true then @true
|
29
|
+
when false then @false
|
20
30
|
else value.to_s
|
21
31
|
end
|
22
32
|
end
|
@@ -11,6 +11,14 @@ module ActiveModel
|
|
11
11
|
@limit = limit
|
12
12
|
end
|
13
13
|
|
14
|
+
# Returns true if this type can convert +value+ to a type that is usable
|
15
|
+
# by the database. For example a boolean type can return +true+ if the
|
16
|
+
# value parameter is a Ruby boolean, but may return +false+ if the value
|
17
|
+
# parameter is some other object.
|
18
|
+
def serializable?(value)
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
14
22
|
def type # :nodoc:
|
15
23
|
end
|
16
24
|
|
@@ -110,7 +118,7 @@ module ActiveModel
|
|
110
118
|
[self.class, precision, scale, limit].hash
|
111
119
|
end
|
112
120
|
|
113
|
-
def assert_valid_value(
|
121
|
+
def assert_valid_value(_)
|
114
122
|
end
|
115
123
|
|
116
124
|
private
|
@@ -15,7 +15,7 @@ module ActiveModel
|
|
15
15
|
# attr_accessor :first_name, :last_name
|
16
16
|
#
|
17
17
|
# validates_each :first_name, :last_name do |record, attr, value|
|
18
|
-
# record.errors.add attr,
|
18
|
+
# record.errors.add attr, "starts with z." if value.start_with?("z")
|
19
19
|
# end
|
20
20
|
# end
|
21
21
|
#
|
@@ -61,7 +61,7 @@ module ActiveModel
|
|
61
61
|
# attr_accessor :first_name, :last_name
|
62
62
|
#
|
63
63
|
# validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
|
64
|
-
# record.errors.add attr,
|
64
|
+
# record.errors.add attr, "starts with z." if value.start_with?("z")
|
65
65
|
# end
|
66
66
|
# end
|
67
67
|
#
|
@@ -163,10 +163,10 @@ module ActiveModel
|
|
163
163
|
if options.key?(:on)
|
164
164
|
options = options.dup
|
165
165
|
options[:on] = Array(options[:on])
|
166
|
-
options[:if] =
|
167
|
-
|
168
|
-
|
169
|
-
|
166
|
+
options[:if] = [
|
167
|
+
->(o) { !(options[:on] & Array(o.validation_context)).empty? },
|
168
|
+
*options[:if]
|
169
|
+
]
|
170
170
|
end
|
171
171
|
|
172
172
|
set_callback(:validate, *args, options, &block)
|
@@ -5,7 +5,7 @@ module ActiveModel
|
|
5
5
|
# == \Active \Model Absence Validator
|
6
6
|
class AbsenceValidator < EachValidator #:nodoc:
|
7
7
|
def validate_each(record, attr_name, value)
|
8
|
-
record.errors.add(attr_name, :present, options) if value.present?
|
8
|
+
record.errors.add(attr_name, :present, **options) if value.present?
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -10,7 +10,7 @@ module ActiveModel
|
|
10
10
|
|
11
11
|
def validate_each(record, attribute, value)
|
12
12
|
unless acceptable_option?(value)
|
13
|
-
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
|
13
|
+
record.errors.add(attribute, :accepted, **options.except(:accept, :allow_nil))
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -56,14 +56,7 @@ module ActiveModel
|
|
56
56
|
def before_validation(*args, &block)
|
57
57
|
options = args.extract_options!
|
58
58
|
|
59
|
-
|
60
|
-
options = options.dup
|
61
|
-
options[:on] = Array(options[:on])
|
62
|
-
options[:if] = Array(options[:if])
|
63
|
-
options[:if].unshift ->(o) {
|
64
|
-
!(options[:on] & Array(o.validation_context)).empty?
|
65
|
-
}
|
66
|
-
end
|
59
|
+
set_options_for_callback(options)
|
67
60
|
|
68
61
|
set_callback(:validation, :before, *args, options, &block)
|
69
62
|
end
|
@@ -99,16 +92,23 @@ module ActiveModel
|
|
99
92
|
options = options.dup
|
100
93
|
options[:prepend] = true
|
101
94
|
|
102
|
-
|
103
|
-
options[:on] = Array(options[:on])
|
104
|
-
options[:if] = Array(options[:if])
|
105
|
-
options[:if].unshift ->(o) {
|
106
|
-
!(options[:on] & Array(o.validation_context)).empty?
|
107
|
-
}
|
108
|
-
end
|
95
|
+
set_options_for_callback(options)
|
109
96
|
|
110
97
|
set_callback(:validation, :after, *args, options, &block)
|
111
98
|
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def set_options_for_callback(options)
|
102
|
+
if options.key?(:on)
|
103
|
+
options[:on] = Array(options[:on])
|
104
|
+
options[:if] = [
|
105
|
+
->(o) {
|
106
|
+
!(options[:on] & Array(o.validation_context)).empty?
|
107
|
+
},
|
108
|
+
*options[:if]
|
109
|
+
]
|
110
|
+
end
|
111
|
+
end
|
112
112
|
end
|
113
113
|
|
114
114
|
private
|
@@ -24,7 +24,11 @@ module ActiveModel
|
|
24
24
|
delimiter
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
if value.is_a?(Array)
|
28
|
+
value.all? { |v| members.public_send(inclusion_method(members), v) }
|
29
|
+
else
|
30
|
+
members.public_send(inclusion_method(members), value)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
def delimiter
|
@@ -9,10 +9,10 @@ module ActiveModel
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def validate_each(record, attribute, value)
|
12
|
-
unless (confirmed = record.
|
12
|
+
unless (confirmed = record.public_send("#{attribute}_confirmation")).nil?
|
13
13
|
unless confirmation_value_equal?(record, attribute, value, confirmed)
|
14
14
|
human_attribute_name = record.class.human_attribute_name(attribute)
|
15
|
-
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
|
15
|
+
record.errors.add(:"#{attribute}_confirmation", :confirmation, **options.except(:case_sensitive).merge!(attribute: human_attribute_name))
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -9,7 +9,7 @@ module ActiveModel
|
|
9
9
|
|
10
10
|
def validate_each(record, attribute, value)
|
11
11
|
if include?(record, value)
|
12
|
-
record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
|
12
|
+
record.errors.add(attribute, :exclusion, **options.except(:in, :within).merge!(value: value))
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -6,7 +6,7 @@ module ActiveModel
|
|
6
6
|
def validate_each(record, attribute, value)
|
7
7
|
if options[:with]
|
8
8
|
regexp = option_call(record, :with)
|
9
|
-
record_error(record, attribute, :with, value)
|
9
|
+
record_error(record, attribute, :with, value) unless regexp.match?(value.to_s)
|
10
10
|
elsif options[:without]
|
11
11
|
regexp = option_call(record, :without)
|
12
12
|
record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
|
@@ -29,7 +29,7 @@ module ActiveModel
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def record_error(record, attribute, name, value)
|
32
|
-
record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
|
32
|
+
record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value))
|
33
33
|
end
|
34
34
|
|
35
35
|
def check_options_validity(name)
|
@@ -9,7 +9,7 @@ module ActiveModel
|
|
9
9
|
|
10
10
|
def validate_each(record, attribute, value)
|
11
11
|
unless include?(record, value)
|
12
|
-
record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value))
|
12
|
+
record.errors.add(attribute, :inclusion, **options.except(:in, :within).merge!(value: value))
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -51,7 +51,7 @@ module ActiveModel
|
|
51
51
|
when Symbol
|
52
52
|
check_value = record.send(check_value)
|
53
53
|
end
|
54
|
-
next if value_length.
|
54
|
+
next if value_length.public_send(validity_check, check_value)
|
55
55
|
end
|
56
56
|
|
57
57
|
errors_options[:count] = check_value
|
@@ -59,7 +59,7 @@ module ActiveModel
|
|
59
59
|
default_message = options[MESSAGES[key]]
|
60
60
|
errors_options[:message] ||= default_message if default_message
|
61
61
|
|
62
|
-
record.errors.add(attribute, MESSAGES[key], errors_options)
|
62
|
+
record.errors.add(attribute, MESSAGES[key], **errors_options)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
@@ -24,44 +24,24 @@ module ActiveModel
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def validate_each(record, attr_name, value)
|
28
|
-
|
29
|
-
|
30
|
-
if record.respond_to?(came_from_user)
|
31
|
-
if record.public_send(came_from_user)
|
32
|
-
raw_value = record.read_attribute_before_type_cast(attr_name)
|
33
|
-
elsif record.respond_to?(:read_attribute)
|
34
|
-
raw_value = record.read_attribute(attr_name)
|
35
|
-
end
|
36
|
-
else
|
37
|
-
before_type_cast = :"#{attr_name}_before_type_cast"
|
38
|
-
if record.respond_to?(before_type_cast)
|
39
|
-
raw_value = record.public_send(before_type_cast)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
raw_value ||= value
|
43
|
-
|
44
|
-
if record_attribute_changed_in_place?(record, attr_name)
|
45
|
-
raw_value = value
|
46
|
-
end
|
47
|
-
|
48
|
-
unless is_number?(raw_value)
|
49
|
-
record.errors.add(attr_name, :not_a_number, filtered_options(raw_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))
|
50
30
|
return
|
51
31
|
end
|
52
32
|
|
53
|
-
if allow_only_integer?(record) && !is_integer?(
|
54
|
-
record.errors.add(attr_name, :not_an_integer, filtered_options(
|
33
|
+
if allow_only_integer?(record) && !is_integer?(value)
|
34
|
+
record.errors.add(attr_name, :not_an_integer, **filtered_options(value))
|
55
35
|
return
|
56
36
|
end
|
57
37
|
|
58
|
-
value = parse_as_number(
|
38
|
+
value = parse_as_number(value, precision, scale)
|
59
39
|
|
60
40
|
options.slice(*CHECKS.keys).each do |option, option_value|
|
61
41
|
case option
|
62
42
|
when :odd, :even
|
63
|
-
unless value.to_i.
|
64
|
-
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))
|
65
45
|
end
|
66
46
|
else
|
67
47
|
case option_value
|
@@ -71,34 +51,44 @@ module ActiveModel
|
|
71
51
|
option_value = record.send(option_value)
|
72
52
|
end
|
73
53
|
|
74
|
-
option_value = parse_as_number(option_value)
|
54
|
+
option_value = parse_as_number(option_value, precision, scale)
|
75
55
|
|
76
|
-
unless value.
|
77
|
-
record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value))
|
56
|
+
unless value.public_send(CHECKS[option], option_value)
|
57
|
+
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
78
58
|
end
|
79
59
|
end
|
80
60
|
end
|
81
61
|
end
|
82
62
|
|
83
63
|
private
|
84
|
-
def
|
85
|
-
!parse_as_number(raw_value).nil?
|
86
|
-
rescue ArgumentError, TypeError
|
87
|
-
false
|
88
|
-
end
|
89
|
-
|
90
|
-
def parse_as_number(raw_value)
|
64
|
+
def parse_as_number(raw_value, precision, scale)
|
91
65
|
if raw_value.is_a?(Float)
|
92
|
-
raw_value
|
66
|
+
parse_float(raw_value, precision, scale)
|
67
|
+
elsif raw_value.is_a?(BigDecimal)
|
68
|
+
round(raw_value, scale)
|
93
69
|
elsif raw_value.is_a?(Numeric)
|
94
70
|
raw_value
|
95
71
|
elsif is_integer?(raw_value)
|
96
72
|
raw_value.to_i
|
97
73
|
elsif !is_hexadecimal_literal?(raw_value)
|
98
|
-
Kernel.Float(raw_value)
|
74
|
+
parse_float(Kernel.Float(raw_value), precision, scale)
|
99
75
|
end
|
100
76
|
end
|
101
77
|
|
78
|
+
def parse_float(raw_value, precision, scale)
|
79
|
+
round(raw_value, scale).to_d(precision)
|
80
|
+
end
|
81
|
+
|
82
|
+
def round(raw_value, scale)
|
83
|
+
scale ? raw_value.round(scale) : raw_value
|
84
|
+
end
|
85
|
+
|
86
|
+
def is_number?(raw_value, precision, scale)
|
87
|
+
!parse_as_number(raw_value, precision, scale).nil?
|
88
|
+
rescue ArgumentError, TypeError
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
102
92
|
def is_integer?(raw_value)
|
103
93
|
INTEGER_REGEX.match?(raw_value.to_s)
|
104
94
|
end
|
@@ -124,6 +114,27 @@ module ActiveModel
|
|
124
114
|
end
|
125
115
|
end
|
126
116
|
|
117
|
+
def prepare_value_for_validation(value, record, attr_name)
|
118
|
+
return value if record_attribute_changed_in_place?(record, attr_name)
|
119
|
+
|
120
|
+
came_from_user = :"#{attr_name}_came_from_user?"
|
121
|
+
|
122
|
+
if record.respond_to?(came_from_user)
|
123
|
+
if record.public_send(came_from_user)
|
124
|
+
raw_value = record.public_send(:"#{attr_name}_before_type_cast")
|
125
|
+
elsif record.respond_to?(:read_attribute)
|
126
|
+
raw_value = record.read_attribute(attr_name)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
before_type_cast = :"#{attr_name}_before_type_cast"
|
130
|
+
if record.respond_to?(before_type_cast)
|
131
|
+
raw_value = record.public_send(before_type_cast)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
raw_value || value
|
136
|
+
end
|
137
|
+
|
127
138
|
def record_attribute_changed_in_place?(record, attr_name)
|
128
139
|
record.respond_to?(:attribute_changed_in_place?) &&
|
129
140
|
record.attribute_changed_in_place?(attr_name.to_s)
|
@@ -134,7 +145,8 @@ module ActiveModel
|
|
134
145
|
# Validates whether the value of the specified attribute is numeric by
|
135
146
|
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
|
136
147
|
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
|
137
|
-
# (if <tt>only_integer</tt> is set to +true+).
|
148
|
+
# (if <tt>only_integer</tt> is set to +true+). Precision of Kernel.Float values
|
149
|
+
# are guaranteed up to 15 digits.
|
138
150
|
#
|
139
151
|
# class Person < ActiveRecord::Base
|
140
152
|
# validates_numericality_of :value, on: :create
|
@@ -175,6 +187,7 @@ module ActiveModel
|
|
175
187
|
# * <tt>:less_than</tt>
|
176
188
|
# * <tt>:less_than_or_equal_to</tt>
|
177
189
|
# * <tt>:only_integer</tt>
|
190
|
+
# * <tt>:other_than</tt>
|
178
191
|
#
|
179
192
|
# For example:
|
180
193
|
#
|
@@ -4,7 +4,7 @@ module ActiveModel
|
|
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
|
|
@@ -12,6 +12,7 @@ module ActiveModel
|
|
12
12
|
#
|
13
13
|
# Examples of using the default rails validators:
|
14
14
|
#
|
15
|
+
# validates :username, absence: true
|
15
16
|
# validates :terms, acceptance: true
|
16
17
|
# validates :password, confirmation: true
|
17
18
|
# validates :username, exclusion: { in: %w(admin superuser) }
|
@@ -27,7 +28,7 @@ module ActiveModel
|
|
27
28
|
# class EmailValidator < ActiveModel::EachValidator
|
28
29
|
# def validate_each(record, attribute, value)
|
29
30
|
# record.errors.add attribute, (options[:message] || "is not an email") unless
|
30
|
-
#
|
31
|
+
# /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.match?(value)
|
31
32
|
# end
|
32
33
|
# end
|
33
34
|
#
|
@@ -47,7 +48,7 @@ module ActiveModel
|
|
47
48
|
#
|
48
49
|
# class TitleValidator < ActiveModel::EachValidator
|
49
50
|
# def validate_each(record, attribute, value)
|
50
|
-
# record.errors.add attribute, "must start with 'the'" unless
|
51
|
+
# record.errors.add attribute, "must start with 'the'" unless /\Athe/i.match?(value)
|
51
52
|
# end
|
52
53
|
# end
|
53
54
|
#
|
@@ -63,7 +64,7 @@ module ActiveModel
|
|
63
64
|
# and strings in shortcut form.
|
64
65
|
#
|
65
66
|
# validates :email, format: /@/
|
66
|
-
# validates :role, inclusion: %(admin contributor)
|
67
|
+
# validates :role, inclusion: %w(admin contributor)
|
67
68
|
# validates :password, length: 6..20
|
68
69
|
#
|
69
70
|
# When using shortcut form, ranges and arrays are passed to your
|
@@ -112,7 +113,6 @@ module ActiveModel
|
|
112
113
|
defaults[:attributes] = attributes
|
113
114
|
|
114
115
|
validations.each do |key, options|
|
115
|
-
next unless options
|
116
116
|
key = "#{key.to_s.camelize}Validator"
|
117
117
|
|
118
118
|
begin
|
@@ -121,6 +121,8 @@ module ActiveModel
|
|
121
121
|
raise ArgumentError, "Unknown validator: '#{key}'"
|
122
122
|
end
|
123
123
|
|
124
|
+
next unless options
|
125
|
+
|
124
126
|
validates_with(validator, defaults.merge(_parse_validates_options(options)))
|
125
127
|
end
|
126
128
|
end
|