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.
Files changed (45) hide show
  1. data/CHANGELOG +44 -1
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +184 -0
  4. data/lib/active_model.rb +29 -19
  5. data/lib/active_model/attribute_methods.rb +167 -46
  6. data/lib/active_model/callbacks.rb +134 -0
  7. data/lib/active_model/conversion.rb +41 -1
  8. data/lib/active_model/deprecated_error_methods.rb +1 -1
  9. data/lib/active_model/dirty.rb +56 -12
  10. data/lib/active_model/errors.rb +205 -46
  11. data/lib/active_model/lint.rb +53 -17
  12. data/lib/active_model/locale/en.yml +26 -23
  13. data/lib/active_model/mass_assignment_security.rb +160 -0
  14. data/lib/active_model/mass_assignment_security/permission_set.rb +40 -0
  15. data/lib/active_model/mass_assignment_security/sanitizer.rb +23 -0
  16. data/lib/active_model/naming.rb +70 -5
  17. data/lib/active_model/observing.rb +40 -16
  18. data/lib/active_model/railtie.rb +2 -0
  19. data/lib/active_model/serialization.rb +59 -0
  20. data/lib/active_model/serializers/json.rb +17 -11
  21. data/lib/active_model/serializers/xml.rb +66 -123
  22. data/lib/active_model/test_case.rb +0 -2
  23. data/lib/active_model/translation.rb +64 -0
  24. data/lib/active_model/validations.rb +150 -68
  25. data/lib/active_model/validations/acceptance.rb +53 -33
  26. data/lib/active_model/validations/callbacks.rb +57 -0
  27. data/lib/active_model/validations/confirmation.rb +41 -23
  28. data/lib/active_model/validations/exclusion.rb +18 -13
  29. data/lib/active_model/validations/format.rb +28 -24
  30. data/lib/active_model/validations/inclusion.rb +18 -13
  31. data/lib/active_model/validations/length.rb +67 -65
  32. data/lib/active_model/validations/numericality.rb +83 -58
  33. data/lib/active_model/validations/presence.rb +10 -8
  34. data/lib/active_model/validations/validates.rb +110 -0
  35. data/lib/active_model/validations/with.rb +90 -23
  36. data/lib/active_model/validator.rb +186 -0
  37. data/lib/active_model/version.rb +3 -2
  38. metadata +79 -20
  39. data/README +0 -21
  40. data/lib/active_model/state_machine.rb +0 -70
  41. data/lib/active_model/state_machine/event.rb +0 -62
  42. data/lib/active_model/state_machine/machine.rb +0 -75
  43. data/lib/active_model/state_machine/state.rb +0 -47
  44. data/lib/active_model/state_machine/state_transition.rb +0 -40
  45. 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
- module ClassMethods
4
- # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
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, :message => "should match confirmation"
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 as an in-memory attribute for validating the password.
17
- # To achieve this, the validation adds accessors to the model for the confirmation attribute. NOTE: This check is performed
18
- # only if +password_confirmation+ is not +nil+, and by default only on save. To require confirmation, make sure to add a presence
19
- # check for the confirmation attribute:
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 confirmation").
25
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
26
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
27
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
28
- # method, proc or string should return or evaluate to a true or false value.
29
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
30
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
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
- configuration = attr_names.extract_options!
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
- module ClassMethods
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 {{value}} is not allowed"
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
- configuration = attr_names.extract_options!
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
- module ClassMethods
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
- configuration = attr_names.extract_options!
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
- module ClassMethods
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 {{value}} is not included in the list"
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
- configuration = attr_names.extract_options!
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
- module ClassMethods
4
- ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
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 {{count}} if you don't mind"
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 :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character"
15
- # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me."
16
- # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
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 {{count}} characters)").
28
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
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 {{count}} characters)").
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(*attrs)
42
- # Merge given options with defaults.
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
- module ClassMethods
4
- ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
5
- :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
6
- :odd => 'odd?', :even => 'even?' }.freeze
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
- configuration = { :only_integer => false, :allow_nil => false }
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