activemodel 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|