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
|
module ActiveModel
# == Active \Model Length Validator
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
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 attribute matches the length restrictions
# supplied. Only one option can be used at a time:
#
# 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 :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
# tokenizer: ->(str) { str.scan(/\w+/) }
# end
#
# Configuration 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>.
# * <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.
# * <tt>:tokenizer</tt> - A method (as a symbol), proc or string to
# specify how to split up the attribute string. (e.g.
# <tt>tokenizer: :word_tokenizer</tt> to call the +word_tokenizer+ method
# or <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words as in
# above example). Defaults to <tt>->(value) { value.split(//) }</tt> which
# counts individual characters.
#
# 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
|