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,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
# == \Active \Model Absence Validator
|
6
|
+
class AbsenceValidator < EachValidator #:nodoc:
|
7
|
+
def validate_each(record, attr_name, value)
|
8
|
+
record.errors.add(attr_name, :present, options) if value.present?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module HelperMethods
|
13
|
+
# Validates that the specified attributes are blank (as defined by
|
14
|
+
# Object#blank?). Happens by default on save.
|
15
|
+
#
|
16
|
+
# class Person < ActiveRecord::Base
|
17
|
+
# validates_absence_of :first_name
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# The first_name attribute must be in the object and it must be blank.
|
21
|
+
#
|
22
|
+
# Configuration options:
|
23
|
+
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
|
24
|
+
#
|
25
|
+
# There is also a list of default options supported by every validator:
|
26
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
27
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
28
|
+
def validates_absence_of(*attr_names)
|
29
|
+
validates_with AbsenceValidator, _merge_attributes(attr_names)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class AcceptanceValidator < EachValidator # :nodoc:
|
6
|
+
def initialize(options)
|
7
|
+
super({ allow_nil: true, accept: ["1", true] }.merge!(options))
|
8
|
+
setup!(options[:class])
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
unless acceptable_option?(value)
|
13
|
+
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def setup!(klass)
|
20
|
+
klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes)))
|
21
|
+
end
|
22
|
+
|
23
|
+
def acceptable_option?(value)
|
24
|
+
Array(options[:accept]).include?(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
class LazilyDefineAttributes < Module
|
28
|
+
def initialize(attribute_definition)
|
29
|
+
define_method(:respond_to_missing?) do |method_name, include_private = false|
|
30
|
+
super(method_name, include_private) || attribute_definition.matches?(method_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method(:method_missing) do |method_name, *args, &block|
|
34
|
+
if attribute_definition.matches?(method_name)
|
35
|
+
attribute_definition.define_on(self.class)
|
36
|
+
send(method_name, *args, &block)
|
37
|
+
else
|
38
|
+
super(method_name, *args, &block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class AttributeDefinition
|
45
|
+
def initialize(attributes)
|
46
|
+
@attributes = attributes.map(&:to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def matches?(method_name)
|
50
|
+
attr_name = convert_to_reader_name(method_name)
|
51
|
+
attributes.include?(attr_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_on(klass)
|
55
|
+
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
56
|
+
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
57
|
+
klass.define_attribute_methods
|
58
|
+
klass.attr_reader(*attr_readers)
|
59
|
+
klass.attr_writer(*attr_writers)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
attr_reader :attributes
|
64
|
+
|
65
|
+
def convert_to_reader_name(method_name)
|
66
|
+
method_name.to_s.chomp("=")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module HelperMethods
|
72
|
+
# Encapsulates the pattern of wanting to validate the acceptance of a
|
73
|
+
# terms of service check box (or similar agreement).
|
74
|
+
#
|
75
|
+
# class Person < ActiveRecord::Base
|
76
|
+
# validates_acceptance_of :terms_of_service
|
77
|
+
# validates_acceptance_of :eula, message: 'must be abided'
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# If the database column does not exist, the +terms_of_service+ attribute
|
81
|
+
# is entirely virtual. This check is performed only if +terms_of_service+
|
82
|
+
# is not +nil+ and by default on save.
|
83
|
+
#
|
84
|
+
# Configuration options:
|
85
|
+
# * <tt>:message</tt> - A custom error message (default is: "must be
|
86
|
+
# accepted").
|
87
|
+
# * <tt>:accept</tt> - Specifies a value that is considered accepted.
|
88
|
+
# Also accepts an array of possible values. The default value is
|
89
|
+
# an array ["1", true], which makes it easy to relate to an HTML
|
90
|
+
# checkbox. This should be set to, or include, +true+ if you are validating
|
91
|
+
# a database column, since the attribute is typecast from "1" to +true+
|
92
|
+
# before validation.
|
93
|
+
#
|
94
|
+
# There is also a list of default options supported by every validator:
|
95
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
96
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information.
|
97
|
+
def validates_acceptance_of(*attr_names)
|
98
|
+
validates_with AcceptanceValidator, _merge_attributes(attr_names)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
# == Active \Model \Validation \Callbacks
|
6
|
+
#
|
7
|
+
# Provides an interface for any class to have +before_validation+ and
|
8
|
+
# +after_validation+ callbacks.
|
9
|
+
#
|
10
|
+
# First, include ActiveModel::Validations::Callbacks from the class you are
|
11
|
+
# creating:
|
12
|
+
#
|
13
|
+
# class MyModel
|
14
|
+
# include ActiveModel::Validations::Callbacks
|
15
|
+
#
|
16
|
+
# before_validation :do_stuff_before_validation
|
17
|
+
# after_validation :do_stuff_after_validation
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Like other <tt>before_*</tt> callbacks if +before_validation+ throws
|
21
|
+
# +:abort+ then <tt>valid?</tt> will not be called.
|
22
|
+
module Callbacks
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
included do
|
26
|
+
include ActiveSupport::Callbacks
|
27
|
+
define_callbacks :validation,
|
28
|
+
skip_after_callbacks_if_terminated: true,
|
29
|
+
scope: [:kind, :name]
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
# Defines a callback that will get called right before validation.
|
34
|
+
#
|
35
|
+
# class Person
|
36
|
+
# include ActiveModel::Validations
|
37
|
+
# include ActiveModel::Validations::Callbacks
|
38
|
+
#
|
39
|
+
# attr_accessor :name
|
40
|
+
#
|
41
|
+
# validates_length_of :name, maximum: 6
|
42
|
+
#
|
43
|
+
# before_validation :remove_whitespaces
|
44
|
+
#
|
45
|
+
# private
|
46
|
+
#
|
47
|
+
# def remove_whitespaces
|
48
|
+
# name.strip!
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# person = Person.new
|
53
|
+
# person.name = ' bob '
|
54
|
+
# person.valid? # => true
|
55
|
+
# person.name # => "bob"
|
56
|
+
def before_validation(*args, &block)
|
57
|
+
options = args.extract_options!
|
58
|
+
|
59
|
+
if options.key?(:on)
|
60
|
+
options = options.dup
|
61
|
+
options[:on] = Array(options[:on])
|
62
|
+
options[:if] = Array(options[:if])
|
63
|
+
options[:if].unshift ->(o) {
|
64
|
+
!(options[:on] & Array(o.validation_context)).empty?
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
set_callback(:validation, :before, *args, options, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Defines a callback that will get called right after validation.
|
72
|
+
#
|
73
|
+
# class Person
|
74
|
+
# include ActiveModel::Validations
|
75
|
+
# include ActiveModel::Validations::Callbacks
|
76
|
+
#
|
77
|
+
# attr_accessor :name, :status
|
78
|
+
#
|
79
|
+
# validates_presence_of :name
|
80
|
+
#
|
81
|
+
# after_validation :set_status
|
82
|
+
#
|
83
|
+
# private
|
84
|
+
#
|
85
|
+
# def set_status
|
86
|
+
# self.status = errors.empty?
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# person = Person.new
|
91
|
+
# person.name = ''
|
92
|
+
# person.valid? # => false
|
93
|
+
# person.status # => false
|
94
|
+
# person.name = 'bob'
|
95
|
+
# person.valid? # => true
|
96
|
+
# person.status # => true
|
97
|
+
def after_validation(*args, &block)
|
98
|
+
options = args.extract_options!
|
99
|
+
options = options.dup
|
100
|
+
options[:prepend] = true
|
101
|
+
|
102
|
+
if options.key?(:on)
|
103
|
+
options[:on] = Array(options[:on])
|
104
|
+
options[:if] = Array(options[:if])
|
105
|
+
options[:if].unshift ->(o) {
|
106
|
+
!(options[:on] & Array(o.validation_context)).empty?
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
set_callback(:validation, :after, *args, options, &block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Overwrite run validations to include callbacks.
|
117
|
+
def run_validations!
|
118
|
+
_run_validation_callbacks { super }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/range"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
module Clusivity #:nodoc:
|
8
|
+
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
|
9
|
+
"and must be supplied as the :in (or :within) option of the configuration hash"
|
10
|
+
|
11
|
+
def check_validity!
|
12
|
+
unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
|
13
|
+
raise ArgumentError, ERROR_MESSAGE
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def include?(record, value)
|
20
|
+
members = if delimiter.respond_to?(:call)
|
21
|
+
delimiter.call(record)
|
22
|
+
elsif delimiter.respond_to?(:to_sym)
|
23
|
+
record.send(delimiter)
|
24
|
+
else
|
25
|
+
delimiter
|
26
|
+
end
|
27
|
+
|
28
|
+
members.send(inclusion_method(members), value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delimiter
|
32
|
+
@delimiter ||= options[:in] || options[:within]
|
33
|
+
end
|
34
|
+
|
35
|
+
# After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
|
36
|
+
# possible values in the range for equality, which is slower but more accurate.
|
37
|
+
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
|
38
|
+
# endpoints, which is fast but is only accurate on Numeric, Time, Date,
|
39
|
+
# or DateTime ranges.
|
40
|
+
def inclusion_method(enumerable)
|
41
|
+
if enumerable.is_a? Range
|
42
|
+
case enumerable.first
|
43
|
+
when Numeric, Time, DateTime, Date
|
44
|
+
:cover?
|
45
|
+
else
|
46
|
+
:include?
|
47
|
+
end
|
48
|
+
else
|
49
|
+
:include?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class ConfirmationValidator < EachValidator # :nodoc:
|
6
|
+
def initialize(options)
|
7
|
+
super({ case_sensitive: true }.merge!(options))
|
8
|
+
setup!(options[:class])
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
unless (confirmed = record.send("#{attribute}_confirmation")).nil?
|
13
|
+
unless confirmation_value_equal?(record, attribute, value, confirmed)
|
14
|
+
human_attribute_name = record.class.human_attribute_name(attribute)
|
15
|
+
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def setup!(klass)
|
22
|
+
klass.attr_reader(*attributes.map do |attribute|
|
23
|
+
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
|
24
|
+
end.compact)
|
25
|
+
|
26
|
+
klass.attr_writer(*attributes.map do |attribute|
|
27
|
+
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
|
28
|
+
end.compact)
|
29
|
+
end
|
30
|
+
|
31
|
+
def confirmation_value_equal?(record, attribute, value, confirmed)
|
32
|
+
if !options[:case_sensitive] && value.is_a?(String)
|
33
|
+
value.casecmp(confirmed) == 0
|
34
|
+
else
|
35
|
+
value == confirmed
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module HelperMethods
|
41
|
+
# Encapsulates the pattern of wanting to validate a password or email
|
42
|
+
# address field with a confirmation.
|
43
|
+
#
|
44
|
+
# Model:
|
45
|
+
# class Person < ActiveRecord::Base
|
46
|
+
# validates_confirmation_of :user_name, :password
|
47
|
+
# validates_confirmation_of :email_address,
|
48
|
+
# message: 'should match confirmation'
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# View:
|
52
|
+
# <%= password_field "person", "password" %>
|
53
|
+
# <%= password_field "person", "password_confirmation" %>
|
54
|
+
#
|
55
|
+
# The added +password_confirmation+ attribute is virtual; it exists only
|
56
|
+
# as an in-memory attribute for validating the password. To achieve this,
|
57
|
+
# the validation adds accessors to the model for the confirmation
|
58
|
+
# attribute.
|
59
|
+
#
|
60
|
+
# NOTE: This check is performed only if +password_confirmation+ is not
|
61
|
+
# +nil+. To require confirmation, make sure to add a presence check for
|
62
|
+
# the confirmation attribute:
|
63
|
+
#
|
64
|
+
# validates_presence_of :password_confirmation, if: :password_changed?
|
65
|
+
#
|
66
|
+
# Configuration options:
|
67
|
+
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
|
68
|
+
# <tt>%{translated_attribute_name}</tt>").
|
69
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
70
|
+
# non-text columns (+true+ by default).
|
71
|
+
#
|
72
|
+
# There is also a list of default options supported by every validator:
|
73
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
74
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
75
|
+
def validates_confirmation_of(*attr_names)
|
76
|
+
validates_with ConfirmationValidator, _merge_attributes(attr_names)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/validations/clusivity"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
class ExclusionValidator < EachValidator # :nodoc:
|
8
|
+
include Clusivity
|
9
|
+
|
10
|
+
def validate_each(record, attribute, value)
|
11
|
+
if include?(record, value)
|
12
|
+
record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module HelperMethods
|
18
|
+
# Validates that the value of the specified attribute is not in a
|
19
|
+
# particular enumerable object.
|
20
|
+
#
|
21
|
+
# class Person < ActiveRecord::Base
|
22
|
+
# validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
|
23
|
+
# validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
|
24
|
+
# validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
|
25
|
+
# validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
|
26
|
+
# message: 'should not be the same as your username or first name'
|
27
|
+
# validates_exclusion_of :karma, in: :reserved_karmas
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Configuration options:
|
31
|
+
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't
|
32
|
+
# be part of. This can be supplied as a proc, lambda or symbol which returns an
|
33
|
+
# enumerable. If the enumerable is a numerical, time or datetime range the test
|
34
|
+
# is performed with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When
|
35
|
+
# using a proc or lambda the instance under validation is passed as an argument.
|
36
|
+
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
|
37
|
+
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
|
38
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
|
39
|
+
# reserved").
|
40
|
+
#
|
41
|
+
# There is also a list of default options supported by every validator:
|
42
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
43
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
44
|
+
def validates_exclusion_of(*attr_names)
|
45
|
+
validates_with ExclusionValidator, _merge_attributes(attr_names)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -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
|