activemodel 3.0.0.beta4 → 3.0.pre

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.
Files changed (39) hide show
  1. data/CHANGELOG +1 -39
  2. data/MIT-LICENSE +1 -1
  3. data/README +16 -200
  4. data/lib/active_model.rb +19 -28
  5. data/lib/active_model/attribute_methods.rb +27 -142
  6. data/lib/active_model/conversion.rb +1 -37
  7. data/lib/active_model/dirty.rb +12 -51
  8. data/lib/active_model/errors.rb +22 -146
  9. data/lib/active_model/lint.rb +14 -48
  10. data/lib/active_model/locale/en.yml +23 -26
  11. data/lib/active_model/naming.rb +5 -41
  12. data/lib/active_model/observing.rb +16 -35
  13. data/lib/active_model/serialization.rb +0 -57
  14. data/lib/active_model/serializers/json.rb +8 -13
  15. data/lib/active_model/serializers/xml.rb +123 -63
  16. data/lib/active_model/state_machine.rb +70 -0
  17. data/lib/active_model/state_machine/event.rb +62 -0
  18. data/lib/active_model/state_machine/machine.rb +75 -0
  19. data/lib/active_model/state_machine/state.rb +47 -0
  20. data/lib/active_model/state_machine/state_transition.rb +40 -0
  21. data/lib/active_model/test_case.rb +2 -0
  22. data/lib/active_model/validations.rb +62 -125
  23. data/lib/active_model/validations/acceptance.rb +18 -23
  24. data/lib/active_model/validations/confirmation.rb +10 -14
  25. data/lib/active_model/validations/exclusion.rb +13 -15
  26. data/lib/active_model/validations/format.rb +24 -26
  27. data/lib/active_model/validations/inclusion.rb +13 -15
  28. data/lib/active_model/validations/length.rb +65 -61
  29. data/lib/active_model/validations/numericality.rb +58 -76
  30. data/lib/active_model/validations/presence.rb +8 -8
  31. data/lib/active_model/validations/with.rb +22 -90
  32. data/lib/active_model/validations_repair_helper.rb +35 -0
  33. data/lib/active_model/version.rb +2 -3
  34. metadata +19 -63
  35. data/lib/active_model/callbacks.rb +0 -134
  36. data/lib/active_model/railtie.rb +0 -2
  37. data/lib/active_model/translation.rb +0 -60
  38. data/lib/active_model/validations/validates.rb +0 -108
  39. data/lib/active_model/validator.rb +0 -183
@@ -1,27 +1,6 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- class AcceptanceValidator < EachValidator
4
- def initialize(options)
5
- super(options.reverse_merge(:allow_nil => true, :accept => "1"))
6
- end
7
-
8
- def validate_each(record, attribute, value)
9
- unless value == options[:accept]
10
- record.errors.add(attribute, :accepted, :default => options[:message])
11
- end
12
- end
13
-
14
- def setup(klass)
15
- # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility
16
- # as instance_methods returns symbols unlike 1.8 which returns strings.
17
- attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
18
- attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
19
- klass.send(:attr_reader, *attr_readers)
20
- klass.send(:attr_writer, *attr_writers)
21
- end
22
- end
23
-
24
- module HelperMethods
3
+ module ClassMethods
25
4
  # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
26
5
  #
27
6
  # class Person < ActiveRecord::Base
@@ -46,7 +25,23 @@ module ActiveModel
46
25
  # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
47
26
  # method, proc or string should return or evaluate to a true or false value.
48
27
  def validates_acceptance_of(*attr_names)
49
- validates_with AcceptanceValidator, _merge_attributes(attr_names)
28
+ configuration = { :allow_nil => true, :accept => "1" }
29
+ configuration.update(attr_names.extract_options!)
30
+
31
+ db_cols = begin
32
+ column_names
33
+ rescue Exception # To ignore both statement and connection errors
34
+ []
35
+ end
36
+
37
+ names = attr_names.reject { |name| db_cols.include?(name.to_s) }
38
+ attr_accessor(*names)
39
+
40
+ validates_each(attr_names,configuration) do |record, attr_name, value|
41
+ unless value == configuration[:accept]
42
+ record.errors.add(attr_name, :accepted, :default => configuration[:message])
43
+ end
44
+ end
50
45
  end
51
46
  end
52
47
  end
@@ -1,18 +1,6 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- class ConfirmationValidator < EachValidator
4
- def validate_each(record, attribute, value)
5
- confirmed = record.send(:"#{attribute}_confirmation")
6
- return if confirmed.nil? || value == confirmed
7
- record.errors.add(attribute, :confirmation, :default => options[:message])
8
- end
9
-
10
- def setup(klass)
11
- klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" })
12
- end
13
- end
14
-
15
- module HelperMethods
3
+ module ClassMethods
16
4
  # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
17
5
  #
18
6
  # Model:
@@ -42,7 +30,15 @@ module ActiveModel
42
30
  # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
43
31
  # method, proc or string should return or evaluate to a true or false value.
44
32
  def validates_confirmation_of(*attr_names)
45
- validates_with ConfirmationValidator, _merge_attributes(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
46
42
  end
47
43
  end
48
44
  end
@@ -1,24 +1,12 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- class ExclusionValidator < EachValidator
4
- def check_validity!
5
- raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
6
- ":in option of the configuration hash" unless options[:in].respond_to?(:include?)
7
- end
8
-
9
- def validate_each(record, attribute, value)
10
- return unless options[:in].include?(value)
11
- record.errors.add(attribute, :exclusion, :default => options[:message], :value => value)
12
- end
13
- end
14
-
15
- module HelperMethods
3
+ module ClassMethods
16
4
  # Validates that the value of the specified attribute is not in a particular enumerable object.
17
5
  #
18
6
  # class Person < ActiveRecord::Base
19
7
  # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
20
8
  # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
21
- # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
9
+ # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
22
10
  # end
23
11
  #
24
12
  # Configuration options:
@@ -33,7 +21,17 @@ module ActiveModel
33
21
  # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
34
22
  # method, proc or string should return or evaluate to a true or false value.
35
23
  def validates_exclusion_of(*attr_names)
36
- validates_with ExclusionValidator, _merge_attributes(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
37
35
  end
38
36
  end
39
37
  end
@@ -1,30 +1,6 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- class FormatValidator < EachValidator
4
- def validate_each(record, attribute, value)
5
- if options[:with] && value.to_s !~ options[:with]
6
- record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
7
- elsif options[:without] && value.to_s =~ options[:without]
8
- record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
9
- end
10
- end
11
-
12
- def check_validity!
13
- unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
14
- raise ArgumentError, "Either :with or :without must be supplied (but not both)"
15
- end
16
-
17
- if options[:with] && !options[:with].is_a?(Regexp)
18
- raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
19
- end
20
-
21
- if options[:without] && !options[:without].is_a?(Regexp)
22
- raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
23
- end
24
- end
25
- end
26
-
27
- module HelperMethods
3
+ module ClassMethods
28
4
  # Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
29
5
  # You can require that the attribute matches the regular expression:
30
6
  #
@@ -57,7 +33,29 @@ module ActiveModel
57
33
  # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
58
34
  # method, proc or string should return or evaluate to a true or false value.
59
35
  def validates_format_of(*attr_names)
60
- validates_with FormatValidator, _merge_attributes(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
61
59
  end
62
60
  end
63
61
  end
@@ -1,24 +1,12 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- class InclusionValidator < EachValidator
4
- def check_validity!
5
- raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
6
- ":in option of the configuration hash" unless options[:in].respond_to?(:include?)
7
- end
8
-
9
- def validate_each(record, attribute, value)
10
- return if options[:in].include?(value)
11
- record.errors.add(attribute, :inclusion, :default => options[:message], :value => value)
12
- end
13
- end
14
-
15
- module HelperMethods
3
+ module ClassMethods
16
4
  # Validates whether the value of the specified attribute is available in a particular enumerable object.
17
5
  #
18
6
  # class Person < ActiveRecord::Base
19
7
  # validates_inclusion_of :gender, :in => %w( m f )
20
8
  # validates_inclusion_of :age, :in => 0..99
21
- # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
9
+ # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
22
10
  # end
23
11
  #
24
12
  # Configuration options:
@@ -33,7 +21,17 @@ module ActiveModel
33
21
  # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
34
22
  # method, proc or string should return or evaluate to a true or false value.
35
23
  def validates_inclusion_of(*attr_names)
36
- validates_with InclusionValidator, _merge_attributes(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
37
35
  end
38
36
  end
39
37
  end
@@ -1,69 +1,19 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- class LengthValidator < EachValidator
4
- MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
5
- CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
6
-
7
- DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
8
-
9
- def initialize(options)
10
- if range = (options.delete(:in) || options.delete(:within))
11
- raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
12
- options[:minimum], options[:maximum] = range.begin, range.end
13
- options[:maximum] -= 1 if range.exclude_end?
14
- end
15
-
16
- super(options.reverse_merge(:tokenizer => DEFAULT_TOKENIZER))
17
- end
18
-
19
- def check_validity!
20
- keys = CHECKS.keys & options.keys
21
-
22
- if keys.empty?
23
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
24
- end
25
-
26
- keys.each do |key|
27
- value = options[key]
28
-
29
- unless value.is_a?(Integer) && value >= 0
30
- raise ArgumentError, ":#{key} must be a nonnegative Integer"
31
- end
32
- end
33
- end
34
-
35
- def validate_each(record, attribute, value)
36
- value = options[:tokenizer].call(value) if value.kind_of?(String)
37
-
38
- CHECKS.each do |key, validity_check|
39
- next unless check_value = options[key]
40
- custom_message = options[:message] || options[MESSAGES[key]]
41
-
42
- valid_value = if key == :maximum
43
- value.nil? || value.size.send(validity_check, check_value)
44
- else
45
- value && value.size.send(validity_check, check_value)
46
- end
47
-
48
- next if valid_value
49
- record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value)
50
- end
51
- end
52
- end
53
-
54
- module HelperMethods
3
+ module ClassMethods
4
+ ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
55
5
 
56
6
  # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
57
7
  #
58
8
  # class Person < ActiveRecord::Base
59
9
  # validates_length_of :first_name, :maximum=>30
60
- # validates_length_of :last_name, :maximum=>30, :message=>"less than 30 if you don't mind"
10
+ # validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind"
61
11
  # validates_length_of :fax, :in => 7..32, :allow_nil => true
62
12
  # validates_length_of :phone, :in => 7..32, :allow_blank => true
63
13
  # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
64
- # validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters"
65
- # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me."
66
- # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
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+/) }
67
17
  # end
68
18
  #
69
19
  # Configuration options:
@@ -74,9 +24,9 @@ module ActiveModel
74
24
  # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
75
25
  # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
76
26
  # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
77
- # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %{count} characters)").
78
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
79
- # * <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)").
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)").
80
30
  # * <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.
81
31
  # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
82
32
  # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@@ -88,8 +38,62 @@ module ActiveModel
88
38
  # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
89
39
  # count words as in above example.)
90
40
  # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
91
- def validates_length_of(*attr_names)
92
- validates_with LengthValidator, _merge_attributes(attr_names)
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
93
97
  end
94
98
 
95
99
  alias_method :validates_size_of, :validates_length_of
@@ -1,81 +1,10 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- class NumericalityValidator < EachValidator
4
- CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
5
- :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
6
- :odd => :odd?, :even => :even? }.freeze
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
7
7
 
8
- def initialize(options)
9
- super(options.reverse_merge(:only_integer => false, :allow_nil => false))
10
- end
11
-
12
- def check_validity!
13
- keys = CHECKS.keys - [:odd, :even]
14
- options.slice(*keys).each do |option, value|
15
- next if value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
16
- raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
17
- end
18
- end
19
-
20
- def validate_each(record, attr_name, value)
21
- before_type_cast = "#{attr_name}_before_type_cast"
22
-
23
- raw_value = record.send("#{attr_name}_before_type_cast") if record.respond_to?(before_type_cast.to_sym)
24
- raw_value ||= value
25
-
26
- return if options[:allow_nil] && raw_value.nil?
27
-
28
- unless value = parse_raw_value_as_a_number(raw_value)
29
- record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message])
30
- return
31
- end
32
-
33
- if options[:only_integer]
34
- unless value = parse_raw_value_as_an_integer(raw_value)
35
- record.errors.add(attr_name, :not_an_integer, :value => raw_value, :default => options[:message])
36
- return
37
- end
38
- end
39
-
40
- options.slice(*CHECKS.keys).each do |option, option_value|
41
- case option
42
- when :odd, :even
43
- unless value.to_i.send(CHECKS[option])
44
- record.errors.add(attr_name, option, :value => value, :default => options[:message])
45
- end
46
- else
47
- option_value = option_value.call(record) if option_value.is_a?(Proc)
48
- option_value = record.send(option_value) if option_value.is_a?(Symbol)
49
-
50
- unless value.send(CHECKS[option], option_value)
51
- record.errors.add(attr_name, option, :default => options[:message], :value => value, :count => option_value)
52
- end
53
- end
54
- end
55
- end
56
-
57
- protected
58
-
59
- def parse_raw_value_as_a_number(raw_value)
60
- case raw_value
61
- when /\A0[xX]/
62
- nil
63
- else
64
- begin
65
- Kernel.Float(raw_value)
66
- rescue ArgumentError, TypeError
67
- nil
68
- end
69
- end
70
- end
71
-
72
- def parse_raw_value_as_an_integer(raw_value)
73
- raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
74
- end
75
-
76
- end
77
-
78
- module HelperMethods
79
8
  # Validates whether the value of the specified attribute is numeric by trying to convert it to
80
9
  # a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
81
10
  # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
@@ -115,8 +44,61 @@ module ActiveModel
115
44
  # validates_numericality_of :width, :greater_than => :minimum_weight
116
45
  # end
117
46
  #
47
+ #
48
+
118
49
  def validates_numericality_of(*attr_names)
119
- validates_with NumericalityValidator, _merge_attributes(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
120
102
  end
121
103
  end
122
104
  end