activemodel 6.0.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 +7 -0
- data/CHANGELOG.md +172 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model.rb +77 -0
- data/lib/active_model/attribute.rb +247 -0
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute_assignment.rb +57 -0
- data/lib/active_model/attribute_methods.rb +517 -0
- data/lib/active_model/attribute_mutation_tracker.rb +178 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attribute_set/builder.rb +124 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attributes.rb +138 -0
- data/lib/active_model/callbacks.rb +156 -0
- data/lib/active_model/conversion.rb +111 -0
- data/lib/active_model/dirty.rb +280 -0
- data/lib/active_model/errors.rb +601 -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 +334 -0
- data/lib/active_model/railtie.rb +20 -0
- data/lib/active_model/secure_password.rb +128 -0
- data/lib/active_model/serialization.rb +192 -0
- data/lib/active_model/serializers/json.rb +147 -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 +47 -0
- data/lib/active_model/type/date.rb +53 -0
- data/lib/active_model/type/date_time.rb +47 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +34 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +45 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +44 -0
- data/lib/active_model/type/helpers/time_value.rb +81 -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 +58 -0
- data/lib/active_model/type/registry.rb +62 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +47 -0
- data/lib/active_model/type/value.rb +126 -0
- data/lib/active_model/validations.rb +437 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +102 -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,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 :role, in: %w( admin contributor )
|
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 non-negative 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.match?(raw_value.to_s)
|
103
|
+
end
|
104
|
+
|
105
|
+
def is_hexadecimal_literal?(raw_value)
|
106
|
+
/\A0[xX]/.match?(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
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class PresenceValidator < EachValidator # :nodoc:
|
6
|
+
def validate_each(record, attr_name, value)
|
7
|
+
record.errors.add(attr_name, :blank, options) if value.blank?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module HelperMethods
|
12
|
+
# Validates that the specified attributes are not blank (as defined by
|
13
|
+
# Object#blank?). Happens by default on save.
|
14
|
+
#
|
15
|
+
# class Person < ActiveRecord::Base
|
16
|
+
# validates_presence_of :first_name
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# The first_name attribute must be in the object and it cannot be blank.
|
20
|
+
#
|
21
|
+
# If you want to validate the presence of a boolean field (where the real
|
22
|
+
# values are +true+ and +false+), you will want to use
|
23
|
+
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
|
24
|
+
#
|
25
|
+
# This is due to the way Object#blank? handles boolean values:
|
26
|
+
# <tt>false.blank? # => true</tt>.
|
27
|
+
#
|
28
|
+
# Configuration options:
|
29
|
+
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
|
30
|
+
#
|
31
|
+
# There is also a list of default options supported by every validator:
|
32
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
33
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
34
|
+
def validates_presence_of(*attr_names)
|
35
|
+
validates_with PresenceValidator, _merge_attributes(attr_names)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/slice"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
module ClassMethods
|
8
|
+
# This method is a shortcut to all default validators and any custom
|
9
|
+
# validator classes ending in 'Validator'. Note that Rails default
|
10
|
+
# validators can be overridden inside specific classes by creating
|
11
|
+
# custom validator classes in their place such as PresenceValidator.
|
12
|
+
#
|
13
|
+
# Examples of using the default rails validators:
|
14
|
+
#
|
15
|
+
# validates :terms, acceptance: true
|
16
|
+
# validates :password, confirmation: true
|
17
|
+
# validates :username, exclusion: { in: %w(admin superuser) }
|
18
|
+
# validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
|
19
|
+
# validates :age, inclusion: { in: 0..9 }
|
20
|
+
# validates :first_name, length: { maximum: 30 }
|
21
|
+
# validates :age, numericality: true
|
22
|
+
# validates :username, presence: true
|
23
|
+
#
|
24
|
+
# The power of the +validates+ method comes when using custom validators
|
25
|
+
# and default validators in one call for a given attribute.
|
26
|
+
#
|
27
|
+
# class EmailValidator < ActiveModel::EachValidator
|
28
|
+
# def validate_each(record, attribute, value)
|
29
|
+
# record.errors.add attribute, (options[:message] || "is not an email") unless
|
30
|
+
# value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class Person
|
35
|
+
# include ActiveModel::Validations
|
36
|
+
# attr_accessor :name, :email
|
37
|
+
#
|
38
|
+
# validates :name, presence: true, length: { maximum: 100 }
|
39
|
+
# validates :email, presence: true, email: true
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# Validator classes may also exist within the class being validated
|
43
|
+
# allowing custom modules of validators to be included as needed.
|
44
|
+
#
|
45
|
+
# class Film
|
46
|
+
# include ActiveModel::Validations
|
47
|
+
#
|
48
|
+
# class TitleValidator < ActiveModel::EachValidator
|
49
|
+
# def validate_each(record, attribute, value)
|
50
|
+
# record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# validates :name, title: true
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Additionally validator classes may be in another namespace and still
|
58
|
+
# used within any class.
|
59
|
+
#
|
60
|
+
# validates :name, :'film/title' => true
|
61
|
+
#
|
62
|
+
# The validators hash can also handle regular expressions, ranges, arrays
|
63
|
+
# and strings in shortcut form.
|
64
|
+
#
|
65
|
+
# validates :email, format: /@/
|
66
|
+
# validates :role, inclusion: %(admin contributor)
|
67
|
+
# validates :password, length: 6..20
|
68
|
+
#
|
69
|
+
# When using shortcut form, ranges and arrays are passed to your
|
70
|
+
# validator's initializer as <tt>options[:in]</tt> while other types
|
71
|
+
# including regular expressions and strings are passed as <tt>options[:with]</tt>.
|
72
|
+
#
|
73
|
+
# There is also a list of options that could be used along with validators:
|
74
|
+
#
|
75
|
+
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
76
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
77
|
+
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
78
|
+
# <tt>on: :custom_validation_context</tt> or
|
79
|
+
# <tt>on: [:create, :custom_validation_context]</tt>)
|
80
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
81
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
82
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
83
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
84
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
|
85
|
+
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
86
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
87
|
+
# method, proc or string should return or evaluate to a +true+ or
|
88
|
+
# +false+ value.
|
89
|
+
# * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
|
90
|
+
# * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
|
91
|
+
# * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true
|
92
|
+
# will raise ActiveModel::StrictValidationFailed instead of adding the error.
|
93
|
+
# <tt>:strict</tt> option can also be set to any other exception.
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
#
|
97
|
+
# validates :password, presence: true, confirmation: true, if: :password_required?
|
98
|
+
# validates :token, length: 24, strict: TokenLengthException
|
99
|
+
#
|
100
|
+
#
|
101
|
+
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
|
102
|
+
# and +:message+ can be given to one specific validator, as a hash:
|
103
|
+
#
|
104
|
+
# validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
|
105
|
+
def validates(*attributes)
|
106
|
+
defaults = attributes.extract_options!.dup
|
107
|
+
validations = defaults.slice!(*_validates_default_keys)
|
108
|
+
|
109
|
+
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
|
110
|
+
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
|
111
|
+
|
112
|
+
defaults[:attributes] = attributes
|
113
|
+
|
114
|
+
validations.each do |key, options|
|
115
|
+
next unless options
|
116
|
+
key = "#{key.to_s.camelize}Validator"
|
117
|
+
|
118
|
+
begin
|
119
|
+
validator = key.include?("::") ? key.constantize : const_get(key)
|
120
|
+
rescue NameError
|
121
|
+
raise ArgumentError, "Unknown validator: '#{key}'"
|
122
|
+
end
|
123
|
+
|
124
|
+
validates_with(validator, defaults.merge(_parse_validates_options(options)))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# This method is used to define validations that cannot be corrected by end
|
129
|
+
# users and are considered exceptional. So each validator defined with bang
|
130
|
+
# or <tt>:strict</tt> option set to <tt>true</tt> will always raise
|
131
|
+
# <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
|
132
|
+
# when validation fails. See <tt>validates</tt> for more information about
|
133
|
+
# the validation itself.
|
134
|
+
#
|
135
|
+
# class Person
|
136
|
+
# include ActiveModel::Validations
|
137
|
+
#
|
138
|
+
# attr_accessor :name
|
139
|
+
# validates! :name, presence: true
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# person = Person.new
|
143
|
+
# person.name = ''
|
144
|
+
# person.valid?
|
145
|
+
# # => ActiveModel::StrictValidationFailed: Name can't be blank
|
146
|
+
def validates!(*attributes)
|
147
|
+
options = attributes.extract_options!
|
148
|
+
options[:strict] = true
|
149
|
+
validates(*(attributes << options))
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# When creating custom validators, it might be useful to be able to specify
|
155
|
+
# additional default keys. This can be done by overwriting this method.
|
156
|
+
def _validates_default_keys
|
157
|
+
[:if, :unless, :on, :allow_blank, :allow_nil, :strict]
|
158
|
+
end
|
159
|
+
|
160
|
+
def _parse_validates_options(options)
|
161
|
+
case options
|
162
|
+
when TrueClass
|
163
|
+
{}
|
164
|
+
when Hash
|
165
|
+
options
|
166
|
+
when Range, Array
|
167
|
+
{ in: options }
|
168
|
+
else
|
169
|
+
{ with: options }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|