1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
require "active_support/core_ext/string/strip"
module ActiveModel
module Validations
class LengthValidator < EachValidator # :nodoc:
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
options[:minimum], options[:maximum] = range.min, range.max
end
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
options[:minimum] = 1
end
if options[:tokenizer]
ActiveSupport::Deprecation.warn(<<-EOS.strip_heredoc)
The `:tokenizer` option is deprecated, and will be removed in Rails 5.1.
You can achieve the same functionality by defining an instance method
with the value that you want to validate the length of. For example,
validates_length_of :essay, minimum: 100,
tokenizer: ->(str) { str.scan(/\w+/) }
should be written as
validates_length_of :words_in_essay, minimum: 100
private
def words_in_essay
essay.scan(/\w+/)
end
EOS
end
super
end
def check_validity!
keys = CHECKS.keys & options.keys
if keys.empty?
raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
end
keys.each do |key|
value = options[key]
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
end
end
end
def validate_each(record, attribute, value)
value = tokenize(record, value)
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
errors_options = options.except(*RESERVED_OPTIONS)
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
if !value.nil? || skip_nil_check?(key)
next if value_length.send(validity_check, check_value)
end
errors_options[:count] = check_value
default_message = options[MESSAGES[key]]
errors_options[:message] ||= default_message if default_message
record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
private
def tokenize(record, value)
tokenizer = options[:tokenizer]
if tokenizer && value.kind_of?(String)
if tokenizer.kind_of?(Proc)
tokenizer.call(value)
elsif record.respond_to?(tokenizer)
record.send(tokenizer, value)
end
end || value
end
def skip_nil_check?(key)
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
end
end
module HelperMethods
# Validates that the specified attributes match the length restrictions
# supplied. Only one constraint option can be used at a time apart from
# +:minimum+ and +:maximum+ that can be combined together:
#
# class Person < ActiveRecord::Base
# validates_length_of :first_name, maximum: 30
# validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
# validates_length_of :fax, in: 7..32, allow_nil: true
# validates_length_of :phone, in: 7..32, allow_blank: true
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
# validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.'
#
# private
#
# def words_in_essay
# essay.scan(/\w+/)
# end
# end
#
# Constraint options:
#
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
# default if not used with +:minimum+.
# * <tt>:is</tt> - The exact size of the attribute.
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
# the attribute.
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
#
# Other options:
#
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
# * <tt>:too_long</tt> - The error message if the attribute goes over the
# maximum (default is: "is too long (maximum is %{count} characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the
# minimum (default is: "is too short (min is %{count} characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
# method and the attribute is the wrong size (default is: "is the wrong
# length (should be %{count} characters)").
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+ and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
alias_method :validates_size_of, :validates_length_of
end
end
end
|