activemodel 3.0.0.beta4 → 3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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