diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index 209b1c76f9..f8759f253b 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -1,6 +1,11 @@ module ActiveModel module Validations class ExclusionValidator < EachValidator + def check_validity! + raise ArgumentError, "An object with the method include? is required must be supplied as the " << + ":in option of the configuration hash" unless options[:in].respond_to?(:include?) + end + def validate_each(record, attribute, value) return unless options[:in].include?(value) record.errors.add(attribute, :exclusion, :default => options[:message], :value => value) @@ -30,10 +35,6 @@ module ActiveModel def validates_exclusion_of(*attr_names) options = attr_names.extract_options! options[:in] ||= options.delete(:within) - - raise ArgumentError, "An object with the method include? is required must be supplied as the " << - ":in option of the configuration hash" unless options[:in].respond_to?(:include?) - validates_with ExclusionValidator, options.merge(:attributes => attr_names) end end diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index d42c95357c..a122e9e737 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -1,6 +1,11 @@ module ActiveModel module Validations class InclusionValidator < EachValidator + def check_validity! + raise ArgumentError, "An object with the method include? is required must be supplied as the " << + ":in option of the configuration hash" unless options[:in].respond_to?(:include?) + end + def validate_each(record, attribute, value) return if options[:in].include?(value) record.errors.add(attribute, :inclusion, :default => options[:message], :value => value) @@ -30,10 +35,6 @@ module ActiveModel def validates_inclusion_of(*attr_names) options = attr_names.extract_options! options[:in] ||= options.delete(:within) - - raise ArgumentError, "An object with the method include? is required must be supplied as the " << - ":in option of the configuration hash" unless options[:in].respond_to?(:include?) - validates_with InclusionValidator, options.merge(:attributes => attr_names) end end diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 66b2ae5b18..1214c5f4bf 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -1,16 +1,24 @@ module ActiveModel module Validations class LengthValidator < EachValidator - MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze - CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze + OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze + MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze + CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze + DEFAULT_TOKENIZER = lambda { |value| value.split(//) } attr_reader :type def initialize(options) - @type = options.delete(:type) + options[:tokenizer] ||= DEFAULT_TOKENIZER super + @type = (OPTIONS & options.keys).first end + def check_validity! + ensure_one_range_option! + ensure_argument_types! + end + def validate_each(record, attribute, value) checks = options.slice(:minimum, :maximum, :is) value = options[:tokenizer].call(value) if value.kind_of?(String) @@ -34,11 +42,35 @@ module ActiveModel record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value) unless valid_value end end + + protected + + def ensure_one_range_option! #:nodoc: + range_options = OPTIONS & options.keys + + case range_options.size + when 0 + raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.' + when 1 + # Valid number of options; do nothing. + else + raise ArgumentError, 'Too many range options specified. Choose only one.' + end + end + + def ensure_argument_types! #:nodoc: + value = options[type] + + case type + when :within, :in + raise ArgumentError, ":#{type} must be a Range" unless value.is_a?(Range) + when :is, :minimum, :maximum + raise ArgumentError, ":#{type} must be a nonnegative Integer" unless value.is_a?(Integer) && value >= 0 + end + end end module ClassMethods - ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze - DEFAULT_TOKENIZER = lambda { |value| value.split(//) } # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time: # @@ -78,28 +110,6 @@ module ActiveModel def validates_length_of(*attr_names) options = { :tokenizer => DEFAULT_TOKENIZER } options.update(attr_names.extract_options!) - - # Ensure that one and only one range option is specified. - range_options = ALL_RANGE_OPTIONS & options.keys - case range_options.size - when 0 - raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.' - when 1 - # Valid number of options; do nothing. - else - raise ArgumentError, 'Too many range options specified. Choose only one.' - end - - type = range_options.first - value = options[type] - - case type - when :within, :in - raise ArgumentError, ":#{type} must be a Range" unless value.is_a?(Range) - when :is, :minimum, :maximum - raise ArgumentError, ":#{type} must be a nonnegative Integer" unless value.is_a?(Integer) && value >= 0 - end - validates_with LengthValidator, options.merge(:attributes => attr_names, :type => type) end diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 8ffa395a2d..914a3133cf 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -1,56 +1,64 @@ module ActiveModel module Validations class NumericalityValidator < EachValidator - CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=', - :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=', - :odd => 'odd?', :even => 'even?' }.freeze + CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=, + :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=, + :odd => :odd?, :even => :even? }.freeze + + def check_validity! + options.slice(*CHECKS.keys) do |option, value| + next if [:odd, :even].include?(option) + 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) + end + end def validate_each(record, attr_name, value) before_type_cast = "#{attr_name}_before_type_cast" - if record.respond_to?(before_type_cast.to_sym) - raw_value = record.send("#{attr_name}_before_type_cast") || value - else - raw_value = value - end + raw_value = record.send("#{attr_name}_before_type_cast") if record.respond_to?(before_type_cast.to_sym) + raw_value ||= value - return if options[:allow_nil] and raw_value.nil? + return if options[:allow_nil] && raw_value.nil? - if options[:only_integer] - unless raw_value.to_s =~ /\A[+-]?\d+\Z/ - record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message]) - return - end - raw_value = raw_value.to_i - else - begin - raw_value = Kernel.Float(raw_value) - rescue ArgumentError, TypeError - record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message]) - return - end + unless value = parse_raw_value(raw_value, options) + record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message]) + return end options.slice(*CHECKS.keys).each do |option, option_value| case option when :odd, :even - unless raw_value.to_i.method(CHECKS[option])[] - record.errors.add(attr_name, option, :value => raw_value, :default => options[:message]) + unless value.to_i.send(CHECKS[option]) + record.errors.add(attr_name, option, :value => value, :default => options[:message]) end else - option_value = option_value.call(record) if option_value.is_a? Proc - option_value = record.method(option_value).call if option_value.is_a? Symbol - - unless raw_value.method(CHECKS[option])[option_value] - record.errors.add(attr_name, option, :default => options[:message], :value => raw_value, :count => option_value) + option_value = option_value.call(record) if option_value.is_a?(Proc) + option_value = record.send(option_value) if option_value.is_a?(Symbol) + + unless value.send(CHECKS[option], option_value) + record.errors.add(attr_name, option, :default => options[:message], :value => value, :count => option_value) end end end end + + protected + + def parse_raw_value(raw_value, options) + if options[:only_integer] + raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/ + else + begin + Kernel.Float(raw_value) + rescue ArgumentError, TypeError + nil + end + end + end + end + module ClassMethods - - # Validates whether the value of the specified attribute is numeric by trying to convert it to # a float with Kernel.Float (if only_integer is false) or applying it to the regular expression # /\A[\+\-]?\d+\Z/ (if only_integer is set to true). @@ -93,14 +101,6 @@ module ActiveModel def validates_numericality_of(*attr_names) options = { :only_integer => false, :allow_nil => false } options.update(attr_names.extract_options!) - - numericality_options = NumericalityValidator::CHECKS.keys & options.keys - - (numericality_options - [ :odd, :even ]).each do |option| - value = options[option] - 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) - end - validates_with NumericalityValidator, options.merge(:attributes => attr_names) end end diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 6885c6800e..342c4691ff 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -70,6 +70,7 @@ module ActiveModel #:nodoc: def initialize(options) @attributes = options.delete(:attributes) super + check_validity! end def validate(record) @@ -83,5 +84,8 @@ module ActiveModel #:nodoc: def validate_each(record) raise NotImplementedError end + + def check_validity! + end end end