activemodel 3.0.pre → 3.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +44 -1
- data/MIT-LICENSE +1 -1
- data/README.rdoc +184 -0
- data/lib/active_model.rb +29 -19
- data/lib/active_model/attribute_methods.rb +167 -46
- data/lib/active_model/callbacks.rb +134 -0
- data/lib/active_model/conversion.rb +41 -1
- data/lib/active_model/deprecated_error_methods.rb +1 -1
- data/lib/active_model/dirty.rb +56 -12
- data/lib/active_model/errors.rb +205 -46
- data/lib/active_model/lint.rb +53 -17
- data/lib/active_model/locale/en.yml +26 -23
- data/lib/active_model/mass_assignment_security.rb +160 -0
- data/lib/active_model/mass_assignment_security/permission_set.rb +40 -0
- data/lib/active_model/mass_assignment_security/sanitizer.rb +23 -0
- data/lib/active_model/naming.rb +70 -5
- data/lib/active_model/observing.rb +40 -16
- data/lib/active_model/railtie.rb +2 -0
- data/lib/active_model/serialization.rb +59 -0
- data/lib/active_model/serializers/json.rb +17 -11
- data/lib/active_model/serializers/xml.rb +66 -123
- data/lib/active_model/test_case.rb +0 -2
- data/lib/active_model/translation.rb +64 -0
- data/lib/active_model/validations.rb +150 -68
- data/lib/active_model/validations/acceptance.rb +53 -33
- data/lib/active_model/validations/callbacks.rb +57 -0
- data/lib/active_model/validations/confirmation.rb +41 -23
- data/lib/active_model/validations/exclusion.rb +18 -13
- data/lib/active_model/validations/format.rb +28 -24
- data/lib/active_model/validations/inclusion.rb +18 -13
- data/lib/active_model/validations/length.rb +67 -65
- data/lib/active_model/validations/numericality.rb +83 -58
- data/lib/active_model/validations/presence.rb +10 -8
- data/lib/active_model/validations/validates.rb +110 -0
- data/lib/active_model/validations/with.rb +90 -23
- data/lib/active_model/validator.rb +186 -0
- data/lib/active_model/version.rb +3 -2
- metadata +79 -20
- data/README +0 -21
- data/lib/active_model/state_machine.rb +0 -70
- data/lib/active_model/state_machine/event.rb +0 -62
- data/lib/active_model/state_machine/machine.rb +0 -75
- data/lib/active_model/state_machine/state.rb +0 -47
- data/lib/active_model/state_machine/state_transition.rb +0 -40
- data/lib/active_model/validations_repair_helper.rb +0 -35
@@ -1,44 +1,62 @@
|
|
1
1
|
module ActiveModel
|
2
|
+
|
3
|
+
# == Active Model Confirmation Validator
|
2
4
|
module Validations
|
3
|
-
|
4
|
-
|
5
|
+
class ConfirmationValidator < EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
confirmed = record.send(:"#{attribute}_confirmation")
|
8
|
+
return if confirmed.nil? || value == confirmed
|
9
|
+
record.errors.add(attribute, :confirmation, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup(klass)
|
13
|
+
klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" })
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module HelperMethods
|
18
|
+
# Encapsulates the pattern of wanting to validate a password or email
|
19
|
+
# address field with a confirmation. For example:
|
5
20
|
#
|
6
21
|
# Model:
|
7
22
|
# class Person < ActiveRecord::Base
|
8
23
|
# validates_confirmation_of :user_name, :password
|
9
|
-
# validates_confirmation_of :email_address,
|
24
|
+
# validates_confirmation_of :email_address,
|
25
|
+
# :message => "should match confirmation"
|
10
26
|
# end
|
11
27
|
#
|
12
28
|
# View:
|
13
29
|
# <%= password_field "person", "password" %>
|
14
30
|
# <%= password_field "person", "password_confirmation" %>
|
15
31
|
#
|
16
|
-
# The added +password_confirmation+ attribute is virtual; it exists only
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
32
|
+
# The added +password_confirmation+ attribute is virtual; it exists only
|
33
|
+
# as an in-memory attribute for validating the password. To achieve this,
|
34
|
+
# the validation adds accessors to the model for the confirmation
|
35
|
+
# attribute.
|
36
|
+
#
|
37
|
+
# NOTE: This check is performed only if +password_confirmation+ is not
|
38
|
+
# +nil+, and by default only on save. To require confirmation, make sure
|
39
|
+
# to add a presence check for the confirmation attribute:
|
20
40
|
#
|
21
41
|
# validates_presence_of :password_confirmation, :if => :password_changed?
|
22
42
|
#
|
23
43
|
# Configuration options:
|
24
|
-
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
|
25
|
-
#
|
26
|
-
# * <tt>:
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
44
|
+
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
|
45
|
+
# confirmation").
|
46
|
+
# * <tt>:on</tt> - Specifies when this validation is active (default is
|
47
|
+
# <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
48
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
49
|
+
# if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
|
50
|
+
# or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
51
|
+
# method, proc or string should return or evaluate to a true or false
|
52
|
+
# value.
|
53
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
54
|
+
# determine if the validation should not occur (e.g.
|
55
|
+
# <tt>:unless => :skip_validation</tt>, or
|
56
|
+
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
31
57
|
# method, proc or string should return or evaluate to a true or false value.
|
32
58
|
def validates_confirmation_of(*attr_names)
|
33
|
-
|
34
|
-
|
35
|
-
attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
|
36
|
-
|
37
|
-
validates_each(attr_names, configuration) do |record, attr_name, value|
|
38
|
-
unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
|
39
|
-
record.errors.add(attr_name, :confirmation, :default => configuration[:message])
|
40
|
-
end
|
41
|
-
end
|
59
|
+
validates_with ConfirmationValidator, _merge_attributes(attr_names)
|
42
60
|
end
|
43
61
|
end
|
44
62
|
end
|
@@ -1,12 +1,27 @@
|
|
1
1
|
module ActiveModel
|
2
|
+
|
3
|
+
# == Active Model Exclusion Validator
|
2
4
|
module Validations
|
3
|
-
|
5
|
+
class ExclusionValidator < EachValidator
|
6
|
+
def check_validity!
|
7
|
+
raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
|
8
|
+
":in option of the configuration hash" unless options[:in].respond_to?(:include?)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
if options[:in].include?(value)
|
13
|
+
record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module HelperMethods
|
4
19
|
# Validates that the value of the specified attribute is not in a particular enumerable object.
|
5
20
|
#
|
6
21
|
# class Person < ActiveRecord::Base
|
7
22
|
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
|
8
23
|
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
|
9
|
-
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {
|
24
|
+
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
|
10
25
|
# end
|
11
26
|
#
|
12
27
|
# Configuration options:
|
@@ -21,17 +36,7 @@ module ActiveModel
|
|
21
36
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
22
37
|
# method, proc or string should return or evaluate to a true or false value.
|
23
38
|
def validates_exclusion_of(*attr_names)
|
24
|
-
|
25
|
-
|
26
|
-
enum = configuration[:in] || configuration[:within]
|
27
|
-
|
28
|
-
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
|
29
|
-
|
30
|
-
validates_each(attr_names, configuration) do |record, attr_name, value|
|
31
|
-
if enum.include?(value)
|
32
|
-
record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
|
33
|
-
end
|
34
|
-
end
|
39
|
+
validates_with ExclusionValidator, _merge_attributes(attr_names)
|
35
40
|
end
|
36
41
|
end
|
37
42
|
end
|
@@ -1,6 +1,32 @@
|
|
1
1
|
module ActiveModel
|
2
|
+
|
3
|
+
# == Active Model Format Validator
|
2
4
|
module Validations
|
3
|
-
|
5
|
+
class FormatValidator < EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
if options[:with] && value.to_s !~ options[:with]
|
8
|
+
record.errors.add(attribute, :invalid, options.except(:with).merge!(:value => value))
|
9
|
+
elsif options[:without] && value.to_s =~ options[:without]
|
10
|
+
record.errors.add(attribute, :invalid, options.except(:without).merge!(:value => value))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_validity!
|
15
|
+
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
|
16
|
+
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
|
17
|
+
end
|
18
|
+
|
19
|
+
if options[:with] && !options[:with].is_a?(Regexp)
|
20
|
+
raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
|
21
|
+
end
|
22
|
+
|
23
|
+
if options[:without] && !options[:without].is_a?(Regexp)
|
24
|
+
raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module HelperMethods
|
4
30
|
# Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
|
5
31
|
# You can require that the attribute matches the regular expression:
|
6
32
|
#
|
@@ -33,29 +59,7 @@ module ActiveModel
|
|
33
59
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
34
60
|
# method, proc or string should return or evaluate to a true or false value.
|
35
61
|
def validates_format_of(*attr_names)
|
36
|
-
|
37
|
-
|
38
|
-
unless configuration.include?(:with) ^ configuration.include?(:without) # ^ == xor, or "exclusive or"
|
39
|
-
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
|
40
|
-
end
|
41
|
-
|
42
|
-
if configuration[:with] && !configuration[:with].is_a?(Regexp)
|
43
|
-
raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
|
44
|
-
end
|
45
|
-
|
46
|
-
if configuration[:without] && !configuration[:without].is_a?(Regexp)
|
47
|
-
raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
|
48
|
-
end
|
49
|
-
|
50
|
-
if configuration[:with]
|
51
|
-
validates_each(attr_names, configuration) do |record, attr_name, value|
|
52
|
-
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) if value.to_s !~ configuration[:with]
|
53
|
-
end
|
54
|
-
elsif configuration[:without]
|
55
|
-
validates_each(attr_names, configuration) do |record, attr_name, value|
|
56
|
-
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) if value.to_s =~ configuration[:without]
|
57
|
-
end
|
58
|
-
end
|
62
|
+
validates_with FormatValidator, _merge_attributes(attr_names)
|
59
63
|
end
|
60
64
|
end
|
61
65
|
end
|
@@ -1,12 +1,27 @@
|
|
1
1
|
module ActiveModel
|
2
|
+
|
3
|
+
# == Active Model Inclusion Validator
|
2
4
|
module Validations
|
3
|
-
|
5
|
+
class InclusionValidator < EachValidator
|
6
|
+
def check_validity!
|
7
|
+
raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
|
8
|
+
":in option of the configuration hash" unless options[:in].respond_to?(:include?)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
unless options[:in].include?(value)
|
13
|
+
record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module HelperMethods
|
4
19
|
# Validates whether the value of the specified attribute is available in a particular enumerable object.
|
5
20
|
#
|
6
21
|
# class Person < ActiveRecord::Base
|
7
22
|
# validates_inclusion_of :gender, :in => %w( m f )
|
8
23
|
# validates_inclusion_of :age, :in => 0..99
|
9
|
-
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {
|
24
|
+
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
|
10
25
|
# end
|
11
26
|
#
|
12
27
|
# Configuration options:
|
@@ -21,17 +36,7 @@ module ActiveModel
|
|
21
36
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
22
37
|
# method, proc or string should return or evaluate to a true or false value.
|
23
38
|
def validates_inclusion_of(*attr_names)
|
24
|
-
|
25
|
-
|
26
|
-
enum = configuration[:in] || configuration[:within]
|
27
|
-
|
28
|
-
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
|
29
|
-
|
30
|
-
validates_each(attr_names, configuration) do |record, attr_name, value|
|
31
|
-
unless enum.include?(value)
|
32
|
-
record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
|
33
|
-
end
|
34
|
-
end
|
39
|
+
validates_with InclusionValidator, _merge_attributes(attr_names)
|
35
40
|
end
|
36
41
|
end
|
37
42
|
end
|
@@ -1,19 +1,75 @@
|
|
1
1
|
module ActiveModel
|
2
|
+
|
3
|
+
# == Active Model Length Validator
|
2
4
|
module Validations
|
3
|
-
|
4
|
-
|
5
|
+
class LengthValidator < EachValidator
|
6
|
+
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
|
7
|
+
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
|
8
|
+
|
9
|
+
DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
|
10
|
+
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
if range = (options.delete(:in) || options.delete(:within))
|
14
|
+
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
|
15
|
+
options[:minimum], options[:maximum] = range.begin, range.end
|
16
|
+
options[:maximum] -= 1 if range.exclude_end?
|
17
|
+
end
|
18
|
+
|
19
|
+
super(options.reverse_merge(:tokenizer => DEFAULT_TOKENIZER))
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_validity!
|
23
|
+
keys = CHECKS.keys & options.keys
|
24
|
+
|
25
|
+
if keys.empty?
|
26
|
+
raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
|
27
|
+
end
|
28
|
+
|
29
|
+
keys.each do |key|
|
30
|
+
value = options[key]
|
31
|
+
|
32
|
+
unless value.is_a?(Integer) && value >= 0
|
33
|
+
raise ArgumentError, ":#{key} must be a nonnegative Integer"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate_each(record, attribute, value)
|
39
|
+
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
40
|
+
|
41
|
+
CHECKS.each do |key, validity_check|
|
42
|
+
next unless check_value = options[key]
|
43
|
+
default_message = options[MESSAGES[key]]
|
44
|
+
options[:message] ||= default_message if default_message
|
45
|
+
|
46
|
+
valid_value = if key == :maximum
|
47
|
+
value.nil? || value.size.send(validity_check, check_value)
|
48
|
+
else
|
49
|
+
value && value.size.send(validity_check, check_value)
|
50
|
+
end
|
51
|
+
|
52
|
+
next if valid_value
|
53
|
+
|
54
|
+
record.errors.add(attribute, MESSAGES[key],
|
55
|
+
options.except(*RESERVED_OPTIONS).merge!(:count => check_value))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module HelperMethods
|
5
61
|
|
6
62
|
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
|
7
63
|
#
|
8
64
|
# class Person < ActiveRecord::Base
|
9
65
|
# validates_length_of :first_name, :maximum=>30
|
10
|
-
# validates_length_of :last_name, :maximum=>30, :message=>"less than
|
66
|
+
# validates_length_of :last_name, :maximum=>30, :message=>"less than 30 if you don't mind"
|
11
67
|
# validates_length_of :fax, :in => 7..32, :allow_nil => true
|
12
68
|
# validates_length_of :phone, :in => 7..32, :allow_blank => true
|
13
69
|
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
|
14
|
-
# validates_length_of :
|
15
|
-
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with
|
16
|
-
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least
|
70
|
+
# validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters"
|
71
|
+
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me."
|
72
|
+
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
|
17
73
|
# end
|
18
74
|
#
|
19
75
|
# Configuration options:
|
@@ -24,9 +80,9 @@ module ActiveModel
|
|
24
80
|
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
|
25
81
|
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
26
82
|
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
27
|
-
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {
|
28
|
-
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {
|
29
|
-
# * <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 {
|
83
|
+
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %{count} characters)").
|
84
|
+
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
|
85
|
+
# * <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)").
|
30
86
|
# * <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.
|
31
87
|
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
32
88
|
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
@@ -38,62 +94,8 @@ module ActiveModel
|
|
38
94
|
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
|
39
95
|
# count words as in above example.)
|
40
96
|
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
|
41
|
-
def validates_length_of(*
|
42
|
-
|
43
|
-
options = { :tokenizer => lambda {|value| value.split(//)} }
|
44
|
-
options.update(attrs.extract_options!.symbolize_keys)
|
45
|
-
|
46
|
-
# Ensure that one and only one range option is specified.
|
47
|
-
range_options = ALL_RANGE_OPTIONS & options.keys
|
48
|
-
case range_options.size
|
49
|
-
when 0
|
50
|
-
raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
|
51
|
-
when 1
|
52
|
-
# Valid number of options; do nothing.
|
53
|
-
else
|
54
|
-
raise ArgumentError, 'Too many range options specified. Choose only one.'
|
55
|
-
end
|
56
|
-
|
57
|
-
# Get range option and value.
|
58
|
-
option = range_options.first
|
59
|
-
option_value = options[range_options.first]
|
60
|
-
key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
|
61
|
-
custom_message = options[:message] || options[key]
|
62
|
-
|
63
|
-
case option
|
64
|
-
when :within, :in
|
65
|
-
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
|
66
|
-
|
67
|
-
validates_each(attrs, options) do |record, attr, value|
|
68
|
-
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
69
|
-
|
70
|
-
min, max = option_value.begin, option_value.end
|
71
|
-
max = max - 1 if option_value.exclude_end?
|
72
|
-
|
73
|
-
if value.nil? || value.size < min
|
74
|
-
record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => min)
|
75
|
-
elsif value.size > max
|
76
|
-
record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => max)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
when :is, :minimum, :maximum
|
80
|
-
raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
|
81
|
-
|
82
|
-
# Declare different validations per option.
|
83
|
-
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
|
84
|
-
|
85
|
-
validates_each(attrs, options) do |record, attr, value|
|
86
|
-
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
87
|
-
|
88
|
-
valid_value = if option == :maximum
|
89
|
-
value.nil? || value.size.send(validity_checks[option], option_value)
|
90
|
-
else
|
91
|
-
value && value.size.send(validity_checks[option], option_value)
|
92
|
-
end
|
93
|
-
|
94
|
-
record.errors.add(attr, key, :default => custom_message, :count => option_value) unless valid_value
|
95
|
-
end
|
96
|
-
end
|
97
|
+
def validates_length_of(*attr_names)
|
98
|
+
validates_with LengthValidator, _merge_attributes(attr_names)
|
97
99
|
end
|
98
100
|
|
99
101
|
alias_method :validates_size_of, :validates_length_of
|
@@ -1,10 +1,88 @@
|
|
1
1
|
module ActiveModel
|
2
|
+
|
3
|
+
# == Active Model Numericality Validator
|
2
4
|
module Validations
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
class NumericalityValidator < EachValidator
|
6
|
+
CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
|
7
|
+
:equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
|
8
|
+
:odd => :odd?, :even => :even? }.freeze
|
9
|
+
|
10
|
+
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
super(options.reverse_merge(:only_integer => false, :allow_nil => false))
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_validity!
|
17
|
+
keys = CHECKS.keys - [:odd, :even]
|
18
|
+
options.slice(*keys).each do |option, value|
|
19
|
+
next if 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
|
+
|
24
|
+
def validate_each(record, attr_name, value)
|
25
|
+
before_type_cast = "#{attr_name}_before_type_cast"
|
26
|
+
|
27
|
+
raw_value = record.send("#{attr_name}_before_type_cast") if record.respond_to?(before_type_cast.to_sym)
|
28
|
+
raw_value ||= value
|
29
|
+
|
30
|
+
return if options[:allow_nil] && raw_value.nil?
|
31
|
+
|
32
|
+
unless value = parse_raw_value_as_a_number(raw_value)
|
33
|
+
record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
if options[:only_integer]
|
38
|
+
unless value = parse_raw_value_as_an_integer(raw_value)
|
39
|
+
record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
|
40
|
+
return
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
options.slice(*CHECKS.keys).each do |option, option_value|
|
45
|
+
case option
|
46
|
+
when :odd, :even
|
47
|
+
unless value.to_i.send(CHECKS[option])
|
48
|
+
record.errors.add(attr_name, option, filtered_options(value))
|
49
|
+
end
|
50
|
+
else
|
51
|
+
option_value = option_value.call(record) if option_value.is_a?(Proc)
|
52
|
+
option_value = record.send(option_value) if option_value.is_a?(Symbol)
|
53
|
+
|
54
|
+
unless value.send(CHECKS[option], option_value)
|
55
|
+
record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def parse_raw_value_as_a_number(raw_value)
|
64
|
+
case raw_value
|
65
|
+
when /\A0[xX]/
|
66
|
+
nil
|
67
|
+
else
|
68
|
+
begin
|
69
|
+
Kernel.Float(raw_value)
|
70
|
+
rescue ArgumentError, TypeError
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_raw_value_as_an_integer(raw_value)
|
77
|
+
raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
|
78
|
+
end
|
79
|
+
|
80
|
+
def filtered_options(value)
|
81
|
+
options.except(*RESERVED_OPTIONS).merge!(:value => value)
|
82
|
+
end
|
83
|
+
end
|
7
84
|
|
85
|
+
module HelperMethods
|
8
86
|
# Validates whether the value of the specified attribute is numeric by trying to convert it to
|
9
87
|
# a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
|
10
88
|
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
|
@@ -44,61 +122,8 @@ module ActiveModel
|
|
44
122
|
# validates_numericality_of :width, :greater_than => :minimum_weight
|
45
123
|
# end
|
46
124
|
#
|
47
|
-
#
|
48
|
-
|
49
125
|
def validates_numericality_of(*attr_names)
|
50
|
-
|
51
|
-
configuration.update(attr_names.extract_options!)
|
52
|
-
|
53
|
-
numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
|
54
|
-
|
55
|
-
(numericality_options - [ :odd, :even ]).each do |option|
|
56
|
-
value = configuration[option]
|
57
|
-
raise ArgumentError, ":#{option} must be a number, a symbol or a proc" unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
|
58
|
-
end
|
59
|
-
|
60
|
-
validates_each(attr_names,configuration) do |record, attr_name, value|
|
61
|
-
before_type_cast = "#{attr_name}_before_type_cast"
|
62
|
-
|
63
|
-
if record.respond_to?(before_type_cast.to_sym)
|
64
|
-
raw_value = record.send("#{attr_name}_before_type_cast") || value
|
65
|
-
else
|
66
|
-
raw_value = value
|
67
|
-
end
|
68
|
-
|
69
|
-
next if configuration[:allow_nil] and raw_value.nil?
|
70
|
-
|
71
|
-
if configuration[:only_integer]
|
72
|
-
unless raw_value.to_s =~ /\A[+-]?\d+\Z/
|
73
|
-
record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
|
74
|
-
next
|
75
|
-
end
|
76
|
-
raw_value = raw_value.to_i
|
77
|
-
else
|
78
|
-
begin
|
79
|
-
raw_value = Kernel.Float(raw_value)
|
80
|
-
rescue ArgumentError, TypeError
|
81
|
-
record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
|
82
|
-
next
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
numericality_options.each do |option|
|
87
|
-
case option
|
88
|
-
when :odd, :even
|
89
|
-
unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
|
90
|
-
record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
|
91
|
-
end
|
92
|
-
else
|
93
|
-
configuration[option] = configuration[option].call(record) if configuration[option].is_a? Proc
|
94
|
-
configuration[option] = record.method(configuration[option]).call if configuration[option].is_a? Symbol
|
95
|
-
|
96
|
-
unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
|
97
|
-
record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
126
|
+
validates_with NumericalityValidator, _merge_attributes(attr_names)
|
102
127
|
end
|
103
128
|
end
|
104
129
|
end
|