activemodel 3.0.pre → 3.0.0.rc
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.
- 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
|