activemodel 7.0.8.7 → 7.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -226
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +5 -5
- data/lib/active_model/attribute/user_provided_default.rb +4 -0
- data/lib/active_model/attribute.rb +26 -1
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +102 -63
- data/lib/active_model/attribute_registration.rb +77 -0
- data/lib/active_model/attribute_set.rb +9 -0
- data/lib/active_model/attributes.rb +62 -45
- data/lib/active_model/callbacks.rb +5 -5
- data/lib/active_model/conversion.rb +14 -4
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +134 -13
- data/lib/active_model/error.rb +4 -3
- data/lib/active_model/errors.rb +17 -12
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +4 -3
- data/lib/active_model/model.rb +26 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +61 -23
- data/lib/active_model/serialization.rb +3 -3
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/translation.rb +18 -16
- data/lib/active_model/type/big_integer.rb +23 -1
- data/lib/active_model/type/binary.rb +7 -1
- data/lib/active_model/type/boolean.rb +11 -9
- data/lib/active_model/type/date.rb +28 -2
- data/lib/active_model/type/date_time.rb +45 -3
- data/lib/active_model/type/decimal.rb +39 -1
- data/lib/active_model/type/float.rb +30 -1
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
- data/lib/active_model/type/helpers/numeric.rb +4 -0
- data/lib/active_model/type/helpers/time_value.rb +28 -12
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +9 -1
- data/lib/active_model/type/time.rb +48 -7
- data/lib/active_model/type/value.rb +17 -1
- data/lib/active_model/type.rb +1 -0
- 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 +4 -4
- data/lib/active_model/validations/clusivity.rb +5 -8
- data/lib/active_model/validations/comparability.rb +0 -11
- data/lib/active_model/validations/comparison.rb +15 -7
- data/lib/active_model/validations/confirmation.rb +1 -1
- data/lib/active_model/validations/format.rb +6 -7
- data/lib/active_model/validations/length.rb +10 -8
- data/lib/active_model/validations/numericality.rb +35 -23
- data/lib/active_model/validations/presence.rb +2 -2
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +4 -4
- data/lib/active_model/validations/with.rb +9 -2
- data/lib/active_model/validations.rb +45 -10
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +18 -13
@@ -2,7 +2,46 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
-
|
5
|
+
# = Active Model \Integer \Type
|
6
|
+
#
|
7
|
+
# Attribute type for integer representation. This type is registered under
|
8
|
+
# the +:integer+ key.
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
# include ActiveModel::Attributes
|
12
|
+
#
|
13
|
+
# attribute :age, :integer
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Values are cast using their +to_i+ method, except for blank strings, which
|
17
|
+
# are cast to +nil+. If a +to_i+ method is not defined or raises an error,
|
18
|
+
# the value will be cast to +nil+.
|
19
|
+
#
|
20
|
+
# person = Person.new
|
21
|
+
#
|
22
|
+
# person.age = "18"
|
23
|
+
# person.age # => 18
|
24
|
+
#
|
25
|
+
# person.age = ""
|
26
|
+
# person.age # => nil
|
27
|
+
#
|
28
|
+
# person.age = :not_an_integer
|
29
|
+
# person.age # => nil (because Symbol does not define #to_i)
|
30
|
+
#
|
31
|
+
# Serialization also works under the same principle. Non-numeric strings are
|
32
|
+
# serialized as +nil+, for example.
|
33
|
+
#
|
34
|
+
# Serialization also validates that the integer can be stored using a
|
35
|
+
# limited number of bytes. If it cannot, an ActiveModel::RangeError will be
|
36
|
+
# raised. The default limit is 4 bytes, and can be customized when declaring
|
37
|
+
# an attribute:
|
38
|
+
#
|
39
|
+
# class Person
|
40
|
+
# include ActiveModel::Attributes
|
41
|
+
#
|
42
|
+
# attribute :age, :integer, limit: 6
|
43
|
+
# end
|
44
|
+
class Integer < Value
|
6
45
|
include Helpers::Numeric
|
7
46
|
|
8
47
|
# Column storage size in bytes.
|
@@ -28,6 +67,10 @@ module ActiveModel
|
|
28
67
|
ensure_in_range(super)
|
29
68
|
end
|
30
69
|
|
70
|
+
def serialize_cast_value(value) # :nodoc:
|
71
|
+
ensure_in_range(value)
|
72
|
+
end
|
73
|
+
|
31
74
|
def serializable?(value)
|
32
75
|
cast_value = cast(value)
|
33
76
|
in_range?(cast_value) || begin
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module SerializeCastValue # :nodoc:
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def serialize_cast_value_compatible?
|
10
|
+
return @serialize_cast_value_compatible if defined?(@serialize_cast_value_compatible)
|
11
|
+
@serialize_cast_value_compatible = ancestors.index(instance_method(:serialize_cast_value).owner) <= ancestors.index(instance_method(:serialize).owner)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module DefaultImplementation
|
16
|
+
def serialize_cast_value(value)
|
17
|
+
value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.included(klass)
|
22
|
+
klass.include DefaultImplementation unless klass.method_defined?(:serialize_cast_value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.serialize(type, value)
|
26
|
+
# Using `type.equal?(type.itself_if_...)` is a performant way to also
|
27
|
+
# ensure that `type` is not just a DelegateClass instance (e.g.
|
28
|
+
# ActiveRecord::Type::Serialized) unintentionally delegating
|
29
|
+
# SerializeCastValue methods.
|
30
|
+
if type.equal?((type.itself_if_serialize_cast_value_compatible rescue nil))
|
31
|
+
type.serialize_cast_value(value)
|
32
|
+
else
|
33
|
+
type.serialize(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def itself_if_serialize_cast_value_compatible
|
38
|
+
self if self.class.serialize_cast_value_compatible?
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(...)
|
42
|
+
super
|
43
|
+
self.class.serialize_cast_value_compatible? # eagerly compute
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -4,7 +4,15 @@ require "active_model/type/immutable_string"
|
|
4
4
|
|
5
5
|
module ActiveModel
|
6
6
|
module Type
|
7
|
-
|
7
|
+
# = Active Model \String \Type
|
8
|
+
#
|
9
|
+
# Attribute type for strings. It is registered under the +:string+ key.
|
10
|
+
#
|
11
|
+
# This class is a specialization of ActiveModel::Type::ImmutableString. It
|
12
|
+
# performs coercion in the same way, and can be configured in the same way.
|
13
|
+
# However, it accounts for mutable strings, so dirty tracking can properly
|
14
|
+
# check if a string has changed.
|
15
|
+
class String < ImmutableString
|
8
16
|
def changed_in_place?(raw_old_value, new_value)
|
9
17
|
if new_value.is_a?(::String)
|
10
18
|
raw_old_value != new_value
|
@@ -2,12 +2,45 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
-
|
5
|
+
# = Active Model \Time \Type
|
6
|
+
#
|
7
|
+
# Attribute type for time of day representation. It is registered under the
|
8
|
+
# +:time+ key.
|
9
|
+
#
|
10
|
+
# class Event
|
11
|
+
# include ActiveModel::Attributes
|
12
|
+
#
|
13
|
+
# attribute :start, :time
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# String values are parsed using the ISO 8601 datetime format, but are
|
17
|
+
# normalized to have a date of 2000-01-01 and be in the UTC time zone.
|
18
|
+
#
|
19
|
+
# event = Event.new
|
20
|
+
# event.start = "2004-10-25T01:23:45-06:00"
|
21
|
+
#
|
22
|
+
# event.start.class # => Time
|
23
|
+
# event.start # => 2000-01-01 07:23:45 UTC
|
24
|
+
#
|
25
|
+
# Partial time-only formats are also accepted.
|
26
|
+
#
|
27
|
+
# event.start = "00:01:02+03:00"
|
28
|
+
# event.start # => 1999-12-31 21:01:02 UTC
|
29
|
+
#
|
30
|
+
# The degree of sub-second precision can be customized when declaring an
|
31
|
+
# attribute:
|
32
|
+
#
|
33
|
+
# class Event
|
34
|
+
# include ActiveModel::Attributes
|
35
|
+
#
|
36
|
+
# attribute :start, :time, precision: 4
|
37
|
+
# end
|
38
|
+
class Time < Value
|
6
39
|
include Helpers::Timezone
|
7
|
-
include Helpers::TimeValue
|
8
40
|
include Helpers::AcceptsMultiparameterTime.new(
|
9
41
|
defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
10
42
|
)
|
43
|
+
include Helpers::TimeValue
|
11
44
|
|
12
45
|
def type
|
13
46
|
:time
|
@@ -19,8 +52,12 @@ module ActiveModel
|
|
19
52
|
case value
|
20
53
|
when ::String
|
21
54
|
value = "2000-01-01 #{value}"
|
22
|
-
time_hash =
|
23
|
-
|
55
|
+
time_hash = begin
|
56
|
+
::Date._parse(value)
|
57
|
+
rescue ArgumentError
|
58
|
+
end
|
59
|
+
|
60
|
+
return if time_hash.nil? || time_hash[:hour].nil?
|
24
61
|
when ::Time
|
25
62
|
value = value.change(year: 2000, day: 1, month: 1)
|
26
63
|
end
|
@@ -31,13 +68,17 @@ module ActiveModel
|
|
31
68
|
private
|
32
69
|
def cast_value(value)
|
33
70
|
return apply_seconds_precision(value) unless value.is_a?(::String)
|
34
|
-
return if value.
|
71
|
+
return if value.blank?
|
35
72
|
|
36
73
|
dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ")
|
37
74
|
|
38
75
|
fast_string_to_time(dummy_time_value) || begin
|
39
|
-
time_hash =
|
40
|
-
|
76
|
+
time_hash = begin
|
77
|
+
::Date._parse(dummy_time_value)
|
78
|
+
rescue ArgumentError
|
79
|
+
end
|
80
|
+
|
81
|
+
return if time_hash.nil? || time_hash[:hour].nil?
|
41
82
|
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
42
83
|
end
|
43
84
|
end
|
@@ -2,10 +2,20 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
+
# = Active Model \Value \Type
|
6
|
+
#
|
7
|
+
# The base class for all attribute types. This class also serves as the
|
8
|
+
# default type for attributes that do not specify a type.
|
5
9
|
class Value
|
10
|
+
include SerializeCastValue
|
6
11
|
attr_reader :precision, :scale, :limit
|
7
12
|
|
13
|
+
# Initializes a type with three basic configuration settings: precision,
|
14
|
+
# limit, and scale. The Value base class does not define behavior for
|
15
|
+
# these settings. It uses them for equality comparison and hash key
|
16
|
+
# generation only.
|
8
17
|
def initialize(precision: nil, limit: nil, scale: nil)
|
18
|
+
super()
|
9
19
|
@precision = precision
|
10
20
|
@scale = scale
|
11
21
|
@limit = limit
|
@@ -19,7 +29,9 @@ module ActiveModel
|
|
19
29
|
true
|
20
30
|
end
|
21
31
|
|
22
|
-
|
32
|
+
# Returns the unique type name as a Symbol. Subclasses should override
|
33
|
+
# this method.
|
34
|
+
def type
|
23
35
|
end
|
24
36
|
|
25
37
|
# Converts a value from database input to the appropriate ruby type. The
|
@@ -129,6 +141,10 @@ module ActiveModel
|
|
129
141
|
false
|
130
142
|
end
|
131
143
|
|
144
|
+
def as_json(*)
|
145
|
+
raise NoMethodError
|
146
|
+
end
|
147
|
+
|
132
148
|
private
|
133
149
|
# Convenience method for types which do not need separate type casting
|
134
150
|
# behavior for user and database inputs. Called by Value#cast for
|
data/lib/active_model/type.rb
CHANGED
@@ -11,7 +11,7 @@ module ActiveModel
|
|
11
11
|
|
12
12
|
module HelperMethods
|
13
13
|
# Validates that the specified attributes are blank (as defined by
|
14
|
-
# Object#present?).
|
14
|
+
# Object#present?).
|
15
15
|
#
|
16
16
|
# class Person < ActiveRecord::Base
|
17
17
|
# validates_absence_of :first_name
|
@@ -90,7 +90,7 @@ module ActiveModel
|
|
90
90
|
#
|
91
91
|
# If the database column does not exist, the +terms_of_service+ attribute
|
92
92
|
# is entirely virtual. This check is performed only if +terms_of_service+
|
93
|
-
# is not +nil
|
93
|
+
# is not +nil+.
|
94
94
|
#
|
95
95
|
# Configuration options:
|
96
96
|
# * <tt>:message</tt> - A custom error message (default is: "must be
|
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Validations
|
5
|
-
#
|
5
|
+
# = Active \Model \Validation \Callbacks
|
6
6
|
#
|
7
|
-
# Provides an interface for any class to have
|
8
|
-
#
|
7
|
+
# Provides an interface for any class to have ClassMethods#before_validation and
|
8
|
+
# ClassMethods#after_validation callbacks.
|
9
9
|
#
|
10
|
-
# First, include ActiveModel::Validations::Callbacks from the class you are
|
10
|
+
# First, include +ActiveModel::Validations::Callbacks+ from the class you are
|
11
11
|
# creating:
|
12
12
|
#
|
13
13
|
# class MyModel
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_model/validations/resolve_value"
|
3
4
|
require "active_support/core_ext/range"
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
module Validations
|
7
8
|
module Clusivity # :nodoc:
|
9
|
+
include ResolveValue
|
10
|
+
|
8
11
|
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
|
9
12
|
"and must be supplied as the :in (or :within) option of the configuration hash"
|
10
13
|
|
@@ -16,13 +19,7 @@ module ActiveModel
|
|
16
19
|
|
17
20
|
private
|
18
21
|
def include?(record, value)
|
19
|
-
members =
|
20
|
-
delimiter.call(record)
|
21
|
-
elsif delimiter.respond_to?(:to_sym)
|
22
|
-
record.send(delimiter)
|
23
|
-
else
|
24
|
-
delimiter
|
25
|
-
end
|
22
|
+
members = resolve_value(record, delimiter)
|
26
23
|
|
27
24
|
if value.is_a?(Array)
|
28
25
|
value.all? { |v| members.public_send(inclusion_method(members), v) }
|
@@ -42,7 +39,7 @@ module ActiveModel
|
|
42
39
|
# or DateTime ranges.
|
43
40
|
def inclusion_method(enumerable)
|
44
41
|
if enumerable.is_a? Range
|
45
|
-
case enumerable.
|
42
|
+
case enumerable.begin || enumerable.end
|
46
43
|
when Numeric, Time, DateTime, Date
|
47
44
|
:cover?
|
48
45
|
else
|
@@ -7,17 +7,6 @@ module ActiveModel
|
|
7
7
|
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
|
8
8
|
other_than: :!= }.freeze
|
9
9
|
|
10
|
-
def option_value(record, option_value)
|
11
|
-
case option_value
|
12
|
-
when Proc
|
13
|
-
option_value.call(record)
|
14
|
-
when Symbol
|
15
|
-
record.send(option_value)
|
16
|
-
else
|
17
|
-
option_value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
10
|
def error_options(value, option_value)
|
22
11
|
options.except(*COMPARE_CHECKS.keys).merge!(
|
23
12
|
count: option_value,
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_model/validations/comparability"
|
4
|
+
require "active_model/validations/resolve_value"
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
module Validations
|
7
8
|
class ComparisonValidator < EachValidator # :nodoc:
|
8
9
|
include Comparability
|
10
|
+
include ResolveValue
|
9
11
|
|
10
12
|
def check_validity!
|
11
13
|
unless (options.keys & COMPARE_CHECKS.keys).any?
|
@@ -16,7 +18,7 @@ module ActiveModel
|
|
16
18
|
|
17
19
|
def validate_each(record, attr_name, value)
|
18
20
|
options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value|
|
19
|
-
option_value =
|
21
|
+
option_value = resolve_value(record, raw_option_value)
|
20
22
|
|
21
23
|
if value.nil? || value.blank?
|
22
24
|
return record.errors.add(attr_name, :blank, **error_options(value, option_value))
|
@@ -42,17 +44,23 @@ module ActiveModel
|
|
42
44
|
# Configuration options:
|
43
45
|
# * <tt>:message</tt> - A custom error message (default is: "failed comparison").
|
44
46
|
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
|
45
|
-
# supplied value.
|
47
|
+
# supplied value. The default error message for this option is _"must be
|
48
|
+
# greater than %{count}"_.
|
46
49
|
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
|
47
|
-
# greater than or equal to the supplied value.
|
50
|
+
# greater than or equal to the supplied value. The default error message
|
51
|
+
# for this option is _"must be greater than or equal to %{count}"_.
|
48
52
|
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
|
49
|
-
# value.
|
53
|
+
# value. The default error message for this option is _"must be equal to
|
54
|
+
# %{count}"_.
|
50
55
|
# * <tt>:less_than</tt> - Specifies the value must be less than the
|
51
|
-
# supplied value.
|
56
|
+
# supplied value. The default error message for this option is _"must be
|
57
|
+
# less than %{count}"_.
|
52
58
|
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
|
53
|
-
# than or equal to the supplied value.
|
59
|
+
# than or equal to the supplied value. The default error message for
|
60
|
+
# this option is _"must be less than or equal to %{count}"_.
|
54
61
|
# * <tt>:other_than</tt> - Specifies the value must not be equal to the
|
55
|
-
# supplied value.
|
62
|
+
# supplied value. The default error message for this option is _"must be
|
63
|
+
# other than %{count}"_.
|
56
64
|
#
|
57
65
|
# There is also a list of default options supported by every validator:
|
58
66
|
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
@@ -64,7 +64,7 @@ module ActiveModel
|
|
64
64
|
# validates_presence_of :password_confirmation, if: :password_changed?
|
65
65
|
#
|
66
66
|
# Configuration options:
|
67
|
-
# * <tt>:message</tt> - A custom error message (default is: "doesn
|
67
|
+
# * <tt>:message</tt> - A custom error message (default is: "doesn’t match
|
68
68
|
# <tt>%{translated_attribute_name}</tt>").
|
69
69
|
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
70
70
|
# non-text columns (+true+ by default).
|
@@ -1,14 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_model/validations/resolve_value"
|
4
|
+
|
3
5
|
module ActiveModel
|
4
6
|
module Validations
|
5
7
|
class FormatValidator < EachValidator # :nodoc:
|
8
|
+
include ResolveValue
|
9
|
+
|
6
10
|
def validate_each(record, attribute, value)
|
7
11
|
if options[:with]
|
8
|
-
regexp =
|
12
|
+
regexp = resolve_value(record, options[:with])
|
9
13
|
record_error(record, attribute, :with, value) unless regexp.match?(value.to_s)
|
10
14
|
elsif options[:without]
|
11
|
-
regexp =
|
15
|
+
regexp = resolve_value(record, options[:without])
|
12
16
|
record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
|
13
17
|
end
|
14
18
|
end
|
@@ -23,11 +27,6 @@ module ActiveModel
|
|
23
27
|
end
|
24
28
|
|
25
29
|
private
|
26
|
-
def option_call(record, name)
|
27
|
-
option = options[name]
|
28
|
-
option.respond_to?(:call) ? option.call(record) : option
|
29
|
-
end
|
30
|
-
|
31
30
|
def record_error(record, attribute, name, value)
|
32
31
|
record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value))
|
33
32
|
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_model/validations/resolve_value"
|
4
|
+
|
3
5
|
module ActiveModel
|
4
6
|
module Validations
|
5
7
|
class LengthValidator < EachValidator # :nodoc:
|
8
|
+
include ResolveValue
|
9
|
+
|
6
10
|
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
|
7
11
|
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
|
8
12
|
|
@@ -11,7 +15,8 @@ module ActiveModel
|
|
11
15
|
def initialize(options)
|
12
16
|
if range = (options.delete(:in) || options.delete(:within))
|
13
17
|
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
|
14
|
-
options[:minimum]
|
18
|
+
options[:minimum] = range.min if range.begin
|
19
|
+
options[:maximum] = (range.exclude_end? ? range.end - 1 : range.end) if range.end
|
15
20
|
end
|
16
21
|
|
17
22
|
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
|
@@ -31,7 +36,9 @@ module ActiveModel
|
|
31
36
|
keys.each do |key|
|
32
37
|
value = options[key]
|
33
38
|
|
34
|
-
unless (value.is_a?(Integer) && value >= 0) ||
|
39
|
+
unless (value.is_a?(Integer) && value >= 0) ||
|
40
|
+
value == Float::INFINITY || value == -Float::INFINITY ||
|
41
|
+
value.is_a?(Symbol) || value.is_a?(Proc)
|
35
42
|
raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc"
|
36
43
|
end
|
37
44
|
end
|
@@ -45,12 +52,7 @@ module ActiveModel
|
|
45
52
|
next unless check_value = options[key]
|
46
53
|
|
47
54
|
if !value.nil? || skip_nil_check?(key)
|
48
|
-
|
49
|
-
when Proc
|
50
|
-
check_value = check_value.call(record)
|
51
|
-
when Symbol
|
52
|
-
check_value = record.send(check_value)
|
53
|
-
end
|
55
|
+
check_value = resolve_value(record, check_value)
|
54
56
|
next if value_length.public_send(validity_check, check_value)
|
55
57
|
end
|
56
58
|
|
@@ -1,17 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_model/validations/comparability"
|
4
|
+
require "active_model/validations/resolve_value"
|
4
5
|
require "bigdecimal/util"
|
5
6
|
|
6
7
|
module ActiveModel
|
7
8
|
module Validations
|
8
9
|
class NumericalityValidator < EachValidator # :nodoc:
|
9
10
|
include Comparability
|
11
|
+
include ResolveValue
|
10
12
|
|
11
13
|
RANGE_CHECKS = { in: :in? }
|
12
14
|
NUMBER_CHECKS = { odd: :odd?, even: :even? }
|
13
15
|
|
14
|
-
RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer]
|
16
|
+
RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer, :only_numeric]
|
15
17
|
|
16
18
|
INTEGER_REGEX = /\A[+-]?\d+\z/
|
17
19
|
|
@@ -64,7 +66,7 @@ module ActiveModel
|
|
64
66
|
|
65
67
|
private
|
66
68
|
def option_as_number(record, option_value, precision, scale)
|
67
|
-
parse_as_number(
|
69
|
+
parse_as_number(resolve_value(record, option_value), precision, scale)
|
68
70
|
end
|
69
71
|
|
70
72
|
def parse_as_number(raw_value, precision, scale)
|
@@ -90,6 +92,10 @@ module ActiveModel
|
|
90
92
|
end
|
91
93
|
|
92
94
|
def is_number?(raw_value, precision, scale)
|
95
|
+
if options[:only_numeric] && !raw_value.is_a?(Numeric)
|
96
|
+
return false
|
97
|
+
end
|
98
|
+
|
93
99
|
!parse_as_number(raw_value, precision, scale).nil?
|
94
100
|
rescue ArgumentError, TypeError
|
95
101
|
false
|
@@ -110,14 +116,7 @@ module ActiveModel
|
|
110
116
|
end
|
111
117
|
|
112
118
|
def allow_only_integer?(record)
|
113
|
-
|
114
|
-
when Symbol
|
115
|
-
record.send(options[:only_integer])
|
116
|
-
when Proc
|
117
|
-
options[:only_integer].call(record)
|
118
|
-
else
|
119
|
-
options[:only_integer]
|
120
|
-
end
|
119
|
+
resolve_value(record, options[:only_integer])
|
121
120
|
end
|
122
121
|
|
123
122
|
def prepare_value_for_validation(value, record, attr_name)
|
@@ -149,10 +148,11 @@ module ActiveModel
|
|
149
148
|
|
150
149
|
module HelperMethods
|
151
150
|
# Validates whether the value of the specified attribute is numeric by
|
152
|
-
# trying to convert it to a float with Kernel.Float (if
|
153
|
-
# is +false+) or applying it to the regular
|
154
|
-
# (if <tt>only_integer</tt> is set to
|
155
|
-
# are guaranteed up to 15
|
151
|
+
# trying to convert it to a float with +Kernel.Float+ (if
|
152
|
+
# <tt>only_integer</tt> is +false+) or applying it to the regular
|
153
|
+
# expression <tt>/\A[\+\-]?\d+\z/</tt> (if <tt>only_integer</tt> is set to
|
154
|
+
# +true+). Precision of +Kernel.Float+ values are guaranteed up to 15
|
155
|
+
# digits.
|
156
156
|
#
|
157
157
|
# class Person < ActiveRecord::Base
|
158
158
|
# validates_numericality_of :value, on: :create
|
@@ -162,24 +162,36 @@ module ActiveModel
|
|
162
162
|
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
|
163
163
|
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
|
164
164
|
# integer (default is +false+).
|
165
|
+
# * <tt>:only_numeric</tt> - Specifies whether the value has to be an
|
166
|
+
# instance of Numeric (default is +false+). The default behavior is to
|
167
|
+
# attempt parsing the value if it is a String.
|
165
168
|
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
|
166
169
|
# +false+). Notice that for Integer and Float columns empty strings are
|
167
170
|
# converted to +nil+.
|
168
171
|
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
|
169
|
-
# supplied value.
|
172
|
+
# supplied value. The default error message for this option is _"must be
|
173
|
+
# greater than %{count}"_.
|
170
174
|
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
|
171
|
-
# greater than or equal the supplied value.
|
175
|
+
# greater than or equal the supplied value. The default error message
|
176
|
+
# for this option is _"must be greater than or equal to %{count}"_.
|
172
177
|
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
|
173
|
-
# value.
|
178
|
+
# value. The default error message for this option is _"must be equal to
|
179
|
+
# %{count}"_.
|
174
180
|
# * <tt>:less_than</tt> - Specifies the value must be less than the
|
175
|
-
# supplied value.
|
181
|
+
# supplied value. The default error message for this option is _"must be
|
182
|
+
# less than %{count}"_.
|
176
183
|
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
|
177
|
-
# than or equal the supplied value.
|
184
|
+
# than or equal the supplied value. The default error message for this
|
185
|
+
# option is _"must be less than or equal to %{count}"_.
|
178
186
|
# * <tt>:other_than</tt> - Specifies the value must be other than the
|
179
|
-
# supplied value.
|
180
|
-
#
|
181
|
-
# * <tt>:
|
182
|
-
#
|
187
|
+
# supplied value. The default error message for this option is _"must be
|
188
|
+
# other than %{count}"_.
|
189
|
+
# * <tt>:odd</tt> - Specifies the value must be an odd number. The default
|
190
|
+
# error message for this option is _"must be odd"_.
|
191
|
+
# * <tt>:even</tt> - Specifies the value must be an even number. The
|
192
|
+
# default error message for this option is _"must be even"_.
|
193
|
+
# * <tt>:in</tt> - Check that the value is within a range. The default
|
194
|
+
# error message for this option is _"must be in %{count}"_.
|
183
195
|
#
|
184
196
|
# There is also a list of default options supported by every validator:
|
185
197
|
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
@@ -10,7 +10,7 @@ module ActiveModel
|
|
10
10
|
|
11
11
|
module HelperMethods
|
12
12
|
# Validates that the specified attributes are not blank (as defined by
|
13
|
-
# Object#blank?).
|
13
|
+
# Object#blank?).
|
14
14
|
#
|
15
15
|
# class Person < ActiveRecord::Base
|
16
16
|
# validates_presence_of :first_name
|
@@ -26,7 +26,7 @@ module ActiveModel
|
|
26
26
|
# <tt>false.blank? # => true</tt>.
|
27
27
|
#
|
28
28
|
# Configuration options:
|
29
|
-
# * <tt>:message</tt> - A custom error message (default is: "can
|
29
|
+
# * <tt>:message</tt> - A custom error message (default is: "can’t be blank").
|
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+.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
module ResolveValue # :nodoc:
|
6
|
+
def resolve_value(record, value)
|
7
|
+
case value
|
8
|
+
when Proc
|
9
|
+
if value.arity == 0
|
10
|
+
value.call
|
11
|
+
else
|
12
|
+
value.call(record)
|
13
|
+
end
|
14
|
+
when Symbol
|
15
|
+
record.send(value)
|
16
|
+
else
|
17
|
+
if value.respond_to?(:call)
|
18
|
+
value.call(record)
|
19
|
+
else
|
20
|
+
value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|