activemodel 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +114 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +264 -0
- data/lib/active_model.rb +77 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute/user_provided_default.rb +52 -0
- data/lib/active_model/attribute_assignment.rb +57 -0
- data/lib/active_model/attribute_methods.rb +478 -0
- data/lib/active_model/attribute_mutation_tracker.rb +124 -0
- data/lib/active_model/attribute_set.rb +114 -0
- data/lib/active_model/attribute_set/builder.rb +126 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_model/attributes.rb +111 -0
- data/lib/active_model/callbacks.rb +153 -0
- data/lib/active_model/conversion.rb +111 -0
- data/lib/active_model/dirty.rb +343 -0
- data/lib/active_model/errors.rb +517 -0
- data/lib/active_model/forbidden_attributes_protection.rb +31 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +36 -0
- data/lib/active_model/model.rb +99 -0
- data/lib/active_model/naming.rb +318 -0
- data/lib/active_model/railtie.rb +14 -0
- data/lib/active_model/secure_password.rb +129 -0
- data/lib/active_model/serialization.rb +192 -0
- data/lib/active_model/serializers/json.rb +146 -0
- data/lib/active_model/translation.rb +70 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/type/big_integer.rb +15 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +38 -0
- data/lib/active_model/type/date.rb +57 -0
- data/lib/active_model/type/date_time.rb +51 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +36 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +37 -0
- data/lib/active_model/type/helpers/time_value.rb +68 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/immutable_string.rb +32 -0
- data/lib/active_model/type/integer.rb +70 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +51 -0
- data/lib/active_model/type/value.rb +126 -0
- data/lib/active_model/validations.rb +439 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +106 -0
- data/lib/active_model/validations/callbacks.rb +122 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +114 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +129 -0
- data/lib/active_model/validations/numericality.rb +189 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/validates.rb +174 -0
- data/lib/active_model/validations/with.rb +147 -0
- data/lib/active_model/validator.rb +183 -0
- data/lib/active_model/version.rb +10 -0
- metadata +125 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class FormatValidator < EachValidator # :nodoc:
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
if options[:with]
|
8
|
+
regexp = option_call(record, :with)
|
9
|
+
record_error(record, attribute, :with, value) if value.to_s !~ regexp
|
10
|
+
elsif options[:without]
|
11
|
+
regexp = option_call(record, :without)
|
12
|
+
record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_validity!
|
17
|
+
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
|
18
|
+
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
|
19
|
+
end
|
20
|
+
|
21
|
+
check_options_validity :with
|
22
|
+
check_options_validity :without
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def option_call(record, name)
|
28
|
+
option = options[name]
|
29
|
+
option.respond_to?(:call) ? option.call(record) : option
|
30
|
+
end
|
31
|
+
|
32
|
+
def record_error(record, attribute, name, value)
|
33
|
+
record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_options_validity(name)
|
37
|
+
if option = options[name]
|
38
|
+
if option.is_a?(Regexp)
|
39
|
+
if options[:multiline] != true && regexp_using_multiline_anchors?(option)
|
40
|
+
raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
|
41
|
+
"which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
|
42
|
+
":multiline => true option?"
|
43
|
+
end
|
44
|
+
elsif !option.respond_to?(:call)
|
45
|
+
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def regexp_using_multiline_anchors?(regexp)
|
51
|
+
source = regexp.source
|
52
|
+
source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$"))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module HelperMethods
|
57
|
+
# Validates whether the value of the specified attribute is of the correct
|
58
|
+
# form, going by the regular expression provided. You can require that the
|
59
|
+
# attribute matches the regular expression:
|
60
|
+
#
|
61
|
+
# class Person < ActiveRecord::Base
|
62
|
+
# validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# Alternatively, you can require that the specified attribute does _not_
|
66
|
+
# match the regular expression:
|
67
|
+
#
|
68
|
+
# class Person < ActiveRecord::Base
|
69
|
+
# validates_format_of :email, without: /NOSPAM/
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# You can also provide a proc or lambda which will determine the regular
|
73
|
+
# expression that will be used to validate the attribute.
|
74
|
+
#
|
75
|
+
# class Person < ActiveRecord::Base
|
76
|
+
# # Admin can have number as a first letter in their screen name
|
77
|
+
# validates_format_of :screen_name,
|
78
|
+
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# Note: use <tt>\A</tt> and <tt>\z</tt> to match the start and end of the
|
82
|
+
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
|
83
|
+
#
|
84
|
+
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
|
85
|
+
# the <tt>multiline: true</tt> option in case you use any of these two
|
86
|
+
# anchors in the provided regular expression. In most cases, you should be
|
87
|
+
# using <tt>\A</tt> and <tt>\z</tt>.
|
88
|
+
#
|
89
|
+
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
|
90
|
+
# In addition, both must be a regular expression or a proc or lambda, or
|
91
|
+
# else an exception will be raised.
|
92
|
+
#
|
93
|
+
# Configuration options:
|
94
|
+
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
|
95
|
+
# * <tt>:with</tt> - Regular expression that if the attribute matches will
|
96
|
+
# result in a successful validation. This can be provided as a proc or
|
97
|
+
# lambda returning regular expression which will be called at runtime.
|
98
|
+
# * <tt>:without</tt> - Regular expression that if the attribute does not
|
99
|
+
# match will result in a successful validation. This can be provided as
|
100
|
+
# a proc or lambda returning regular expression which will be called at
|
101
|
+
# runtime.
|
102
|
+
# * <tt>:multiline</tt> - Set to true if your regular expression contains
|
103
|
+
# anchors that match the beginning or end of lines as opposed to the
|
104
|
+
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
|
105
|
+
#
|
106
|
+
# There is also a list of default options supported by every validator:
|
107
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
108
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
109
|
+
def validates_format_of(*attr_names)
|
110
|
+
validates_with FormatValidator, _merge_attributes(attr_names)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
module HelperMethods # :nodoc:
|
6
|
+
private
|
7
|
+
def _merge_attributes(attr_names)
|
8
|
+
options = attr_names.extract_options!.symbolize_keys
|
9
|
+
attr_names.flatten!
|
10
|
+
options[:attributes] = attr_names
|
11
|
+
options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/validations/clusivity"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
class InclusionValidator < EachValidator # :nodoc:
|
8
|
+
include Clusivity
|
9
|
+
|
10
|
+
def validate_each(record, attribute, value)
|
11
|
+
unless include?(record, value)
|
12
|
+
record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module HelperMethods
|
18
|
+
# Validates whether the value of the specified attribute is available in a
|
19
|
+
# particular enumerable object.
|
20
|
+
#
|
21
|
+
# class Person < ActiveRecord::Base
|
22
|
+
# validates_inclusion_of :gender, in: %w( m f )
|
23
|
+
# validates_inclusion_of :age, in: 0..99
|
24
|
+
# validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
|
25
|
+
# validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
|
26
|
+
# validates_inclusion_of :karma, in: :available_karmas
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Configuration options:
|
30
|
+
# * <tt>:in</tt> - An enumerable object of available items. This can be
|
31
|
+
# supplied as a proc, lambda or symbol which returns an enumerable. If the
|
32
|
+
# enumerable is a numerical, time or datetime range the test is performed
|
33
|
+
# with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When using
|
34
|
+
# a proc or lambda the instance under validation is passed as an argument.
|
35
|
+
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
|
36
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
|
37
|
+
# not included in the list").
|
38
|
+
#
|
39
|
+
# There is also a list of default options supported by every validator:
|
40
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
41
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
42
|
+
def validates_inclusion_of(*attr_names)
|
43
|
+
validates_with InclusionValidator, _merge_attributes(attr_names)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class LengthValidator < EachValidator # :nodoc:
|
6
|
+
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
|
7
|
+
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
|
8
|
+
|
9
|
+
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long]
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
if range = (options.delete(:in) || options.delete(:within))
|
13
|
+
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
|
14
|
+
options[:minimum], options[:maximum] = range.min, range.max
|
15
|
+
end
|
16
|
+
|
17
|
+
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
|
18
|
+
options[:minimum] = 1
|
19
|
+
end
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def check_validity!
|
25
|
+
keys = CHECKS.keys & options.keys
|
26
|
+
|
27
|
+
if keys.empty?
|
28
|
+
raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option."
|
29
|
+
end
|
30
|
+
|
31
|
+
keys.each do |key|
|
32
|
+
value = options[key]
|
33
|
+
|
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 nonnegative Integer, Infinity, Symbol, or Proc"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_each(record, attribute, value)
|
41
|
+
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
|
42
|
+
errors_options = options.except(*RESERVED_OPTIONS)
|
43
|
+
|
44
|
+
CHECKS.each do |key, validity_check|
|
45
|
+
next unless check_value = options[key]
|
46
|
+
|
47
|
+
if !value.nil? || skip_nil_check?(key)
|
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.send(validity_check, check_value)
|
55
|
+
end
|
56
|
+
|
57
|
+
errors_options[:count] = check_value
|
58
|
+
|
59
|
+
default_message = options[MESSAGES[key]]
|
60
|
+
errors_options[:message] ||= default_message if default_message
|
61
|
+
|
62
|
+
record.errors.add(attribute, MESSAGES[key], errors_options)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def skip_nil_check?(key)
|
68
|
+
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module HelperMethods
|
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:
|
76
|
+
#
|
77
|
+
# class Person < ActiveRecord::Base
|
78
|
+
# validates_length_of :first_name, maximum: 30
|
79
|
+
# validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
|
80
|
+
# validates_length_of :fax, in: 7..32, allow_nil: true
|
81
|
+
# validates_length_of :phone, in: 7..32, allow_blank: true
|
82
|
+
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
|
83
|
+
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
|
84
|
+
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
|
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
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Constraint options:
|
95
|
+
#
|
96
|
+
# * <tt>:minimum</tt> - The minimum size of the attribute.
|
97
|
+
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
|
98
|
+
# default if not used with +:minimum+.
|
99
|
+
# * <tt>:is</tt> - The exact size of the attribute.
|
100
|
+
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
|
101
|
+
# the attribute.
|
102
|
+
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
|
103
|
+
#
|
104
|
+
# Other options:
|
105
|
+
#
|
106
|
+
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
107
|
+
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
108
|
+
# * <tt>:too_long</tt> - The error message if the attribute goes over the
|
109
|
+
# maximum (default is: "is too long (maximum is %{count} characters)").
|
110
|
+
# * <tt>:too_short</tt> - The error message if the attribute goes under the
|
111
|
+
# minimum (default is: "is too short (minimum is %{count} characters)").
|
112
|
+
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
|
113
|
+
# method and the attribute is the wrong size (default is: "is the wrong
|
114
|
+
# length (should be %{count} characters)").
|
115
|
+
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
|
116
|
+
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
|
117
|
+
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
|
118
|
+
#
|
119
|
+
# There is also a list of default options supported by every validator:
|
120
|
+
# +:if+, +:unless+, +:on+ and +:strict+.
|
121
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
122
|
+
def validates_length_of(*attr_names)
|
123
|
+
validates_with LengthValidator, _merge_attributes(attr_names)
|
124
|
+
end
|
125
|
+
|
126
|
+
alias_method :validates_size_of, :validates_length_of
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bigdecimal/util"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
class NumericalityValidator < EachValidator # :nodoc:
|
8
|
+
CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
|
9
|
+
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
|
10
|
+
odd: :odd?, even: :even?, other_than: :!= }.freeze
|
11
|
+
|
12
|
+
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
|
13
|
+
|
14
|
+
INTEGER_REGEX = /\A[+-]?\d+\z/
|
15
|
+
|
16
|
+
def check_validity!
|
17
|
+
keys = CHECKS.keys - [:odd, :even]
|
18
|
+
options.slice(*keys).each do |option, value|
|
19
|
+
unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
|
20
|
+
raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_each(record, attr_name, value)
|
26
|
+
came_from_user = :"#{attr_name}_came_from_user?"
|
27
|
+
|
28
|
+
if record.respond_to?(came_from_user)
|
29
|
+
if record.public_send(came_from_user)
|
30
|
+
raw_value = record.read_attribute_before_type_cast(attr_name)
|
31
|
+
elsif record.respond_to?(:read_attribute)
|
32
|
+
raw_value = record.read_attribute(attr_name)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
before_type_cast = :"#{attr_name}_before_type_cast"
|
36
|
+
if record.respond_to?(before_type_cast)
|
37
|
+
raw_value = record.public_send(before_type_cast)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
raw_value ||= value
|
41
|
+
|
42
|
+
if record_attribute_changed_in_place?(record, attr_name)
|
43
|
+
raw_value = value
|
44
|
+
end
|
45
|
+
|
46
|
+
unless is_number?(raw_value)
|
47
|
+
record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
if allow_only_integer?(record) && !is_integer?(raw_value)
|
52
|
+
record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
value = parse_as_number(raw_value)
|
57
|
+
|
58
|
+
options.slice(*CHECKS.keys).each do |option, option_value|
|
59
|
+
case option
|
60
|
+
when :odd, :even
|
61
|
+
unless value.to_i.send(CHECKS[option])
|
62
|
+
record.errors.add(attr_name, option, filtered_options(value))
|
63
|
+
end
|
64
|
+
else
|
65
|
+
case option_value
|
66
|
+
when Proc
|
67
|
+
option_value = option_value.call(record)
|
68
|
+
when Symbol
|
69
|
+
option_value = record.send(option_value)
|
70
|
+
end
|
71
|
+
|
72
|
+
option_value = parse_as_number(option_value)
|
73
|
+
|
74
|
+
unless value.send(CHECKS[option], option_value)
|
75
|
+
record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def is_number?(raw_value)
|
84
|
+
!parse_as_number(raw_value).nil?
|
85
|
+
rescue ArgumentError, TypeError
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_as_number(raw_value)
|
90
|
+
if raw_value.is_a?(Float)
|
91
|
+
raw_value.to_d
|
92
|
+
elsif raw_value.is_a?(Numeric)
|
93
|
+
raw_value
|
94
|
+
elsif is_integer?(raw_value)
|
95
|
+
raw_value.to_i
|
96
|
+
elsif !is_hexadecimal_literal?(raw_value)
|
97
|
+
Kernel.Float(raw_value).to_d
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def is_integer?(raw_value)
|
102
|
+
INTEGER_REGEX === raw_value.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
def is_hexadecimal_literal?(raw_value)
|
106
|
+
/\A0[xX]/ === raw_value.to_s
|
107
|
+
end
|
108
|
+
|
109
|
+
def filtered_options(value)
|
110
|
+
filtered = options.except(*RESERVED_OPTIONS)
|
111
|
+
filtered[:value] = value
|
112
|
+
filtered
|
113
|
+
end
|
114
|
+
|
115
|
+
def allow_only_integer?(record)
|
116
|
+
case options[:only_integer]
|
117
|
+
when Symbol
|
118
|
+
record.send(options[:only_integer])
|
119
|
+
when Proc
|
120
|
+
options[:only_integer].call(record)
|
121
|
+
else
|
122
|
+
options[:only_integer]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def record_attribute_changed_in_place?(record, attr_name)
|
127
|
+
record.respond_to?(:attribute_changed_in_place?) &&
|
128
|
+
record.attribute_changed_in_place?(attr_name.to_s)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
module HelperMethods
|
133
|
+
# Validates whether the value of the specified attribute is numeric by
|
134
|
+
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
|
135
|
+
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
|
136
|
+
# (if <tt>only_integer</tt> is set to +true+).
|
137
|
+
#
|
138
|
+
# class Person < ActiveRecord::Base
|
139
|
+
# validates_numericality_of :value, on: :create
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# Configuration options:
|
143
|
+
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
|
144
|
+
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
|
145
|
+
# integer, e.g. an integral value (default is +false+).
|
146
|
+
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
|
147
|
+
# +false+). Notice that for Integer and Float columns empty strings are
|
148
|
+
# converted to +nil+.
|
149
|
+
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
|
150
|
+
# supplied value.
|
151
|
+
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
|
152
|
+
# greater than or equal the supplied value.
|
153
|
+
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
|
154
|
+
# value.
|
155
|
+
# * <tt>:less_than</tt> - Specifies the value must be less than the
|
156
|
+
# supplied value.
|
157
|
+
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
|
158
|
+
# than or equal the supplied value.
|
159
|
+
# * <tt>:other_than</tt> - Specifies the value must be other than the
|
160
|
+
# supplied value.
|
161
|
+
# * <tt>:odd</tt> - Specifies the value must be an odd number.
|
162
|
+
# * <tt>:even</tt> - Specifies the value must be an even number.
|
163
|
+
#
|
164
|
+
# There is also a list of default options supported by every validator:
|
165
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
166
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
167
|
+
#
|
168
|
+
# The following checks can also be supplied with a proc or a symbol which
|
169
|
+
# corresponds to a method:
|
170
|
+
#
|
171
|
+
# * <tt>:greater_than</tt>
|
172
|
+
# * <tt>:greater_than_or_equal_to</tt>
|
173
|
+
# * <tt>:equal_to</tt>
|
174
|
+
# * <tt>:less_than</tt>
|
175
|
+
# * <tt>:less_than_or_equal_to</tt>
|
176
|
+
# * <tt>:only_integer</tt>
|
177
|
+
#
|
178
|
+
# For example:
|
179
|
+
#
|
180
|
+
# class Person < ActiveRecord::Base
|
181
|
+
# validates_numericality_of :width, less_than: ->(person) { person.height }
|
182
|
+
# validates_numericality_of :width, greater_than: :minimum_weight
|
183
|
+
# end
|
184
|
+
def validates_numericality_of(*attr_names)
|
185
|
+
validates_with NumericalityValidator, _merge_attributes(attr_names)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|