Move all the Active Record validations to Active Model

This commit is contained in:
Pratik Naik 2009-03-19 23:28:59 +00:00
parent 6ed42ebdff
commit 8828b2ca67
22 changed files with 555 additions and 1353 deletions

View File

@ -1,5 +1,43 @@
require 'active_model/observing'
# disabled until they're tested
# require 'active_model/callbacks'
# require 'active_model/validations'
require 'active_model/base'
#--
# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
begin
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
end
end
module ActiveModel
def self.load_all!
[Base]
end
autoload :Base, 'active_model/base'
autoload :Validations, 'active_model/validations'
autoload :Errors, 'active_model/errors'
autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
end

View File

@ -1,37 +1,28 @@
module ActiveModel
module DeprecatedErrorMethods
def on(attribute)
ActiveSupport::Deprecation.warn "Errors#on have been deprecated, use Errors#[] instead"
self[attribute]
# ActiveSupport::Deprecation.warn "Errors#on have been deprecated, use Errors#[] instead"
errors = self[attribute]
errors.size < 2 ? errors.first : errors
end
def on_base
ActiveSupport::Deprecation.warn "Errors#on_base have been deprecated, use Errors#[:base] instead"
# ActiveSupport::Deprecation.warn "Errors#on_base have been deprecated, use Errors#[:base] instead"
on(:base)
end
def add(attribute, msg = Errors.default_error_messages[:invalid])
ActiveSupport::Deprecation.warn "Errors#add(attribute, msg) has been deprecated, use Errors#[attribute] << msg instead"
self[attribute] << msg
end
def add_to_base(msg)
ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
# ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
self[:base] << msg
end
def invalid?(attribute)
ActiveSupport::Deprecation.warn "Errors#invalid?(attribute) has been deprecated, use Errors#[attribute].any? instead"
# ActiveSupport::Deprecation.warn "Errors#invalid?(attribute) has been deprecated, use Errors#[attribute].any? instead"
self[attribute].any?
end
def full_messages
ActiveSupport::Deprecation.warn "Errors#full_messages has been deprecated, use Errors#to_a instead"
to_a
end
def each_full
ActiveSupport::Deprecation.warn "Errors#each_full has been deprecated, use Errors#to_a.each instead"
# ActiveSupport::Deprecation.warn "Errors#each_full has been deprecated, use Errors#to_a.each instead"
to_a.each { |error| yield error }
end
end

View File

@ -1,40 +1,27 @@
module ActiveModel
class Errors < Hash
include DeprecatedErrorMethods
@@default_error_messages = {
:inclusion => "is not included in the list",
:exclusion => "is reserved",
:invalid => "is invalid",
:confirmation => "doesn't match confirmation",
:accepted => "must be accepted",
:empty => "can't be empty",
:blank => "can't be blank",
:too_long => "is too long (maximum is %d characters)",
:too_short => "is too short (minimum is %d characters)",
:wrong_length => "is the wrong length (should be %d characters)",
:taken => "has already been taken",
:not_a_number => "is not a number",
:greater_than => "must be greater than %d",
:greater_than_or_equal_to => "must be greater than or equal to %d",
:equal_to => "must be equal to %d",
:less_than => "must be less than %d",
:less_than_or_equal_to => "must be less than or equal to %d",
:odd => "must be odd",
:even => "must be even"
}
##
# :singleton-method:
# Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
cattr_accessor :default_error_messages
class << self
def default_error_messages
message = "Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages')."
ActiveSupport::Deprecation.warn(message)
I18n.translate 'activerecord.errors.messages'
end
end
def initialize(base)
@base = base
super()
end
alias_method :get, :[]
alias_method :set, :[]=
def [](attribute)
if errors = get(attribute.to_sym)
errors.size == 1 ? errors.first : errors
errors
else
set(attribute.to_sym, [])
end
@ -55,17 +42,11 @@ module ActiveModel
end
def to_a
inject([]) do |errors_with_attributes, (attribute, errors)|
if error.blank?
errors_with_attributes
else
if attr == :base
errors_with_attributes << error
else
errors_with_attributes << (attribute.to_s.humanize + " " + error)
end
end
end
full_messages
end
def count
to_a.size
end
def to_xml(options={})
@ -78,5 +59,106 @@ module ActiveModel
to_a.each { |error| e.error(error) }
end
end
# Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
# If no +messsage+ is supplied, :invalid is assumed.
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
def add(attribute, message = nil, options = {})
message ||= :invalid
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
self[attribute] << message
end
# Will add an error message to each of the attributes in +attributes+ that is empty.
def add_on_empty(attributes, custom_message = nil)
[attributes].flatten.each do |attribute|
value = @base.respond_to?(attribute.to_s) ? @base.send(attribute.to_s) : @base[attribute.to_s]
is_empty = value.respond_to?(:empty?) ? value.empty? : false
add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty
end
end
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
def add_on_blank(attributes, custom_message = nil)
[attributes].flatten.each do |attribute|
value = @base.respond_to?(attribute.to_sym) ? @base.send(attribute.to_sym) : @base[attribute.to_sym]
add(attribute, :blank, :default => custom_message) if value.blank?
end
end
# Returns all the full error messages in an array.
#
# class Company < ActiveRecord::Base
# validates_presence_of :name, :address, :email
# validates_length_of :name, :in => 5..30
# end
#
# company = Company.create(:address => '123 First St.')
# company.errors.full_messages # =>
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
def full_messages(options = {})
full_messages = []
each do |attribute, messages|
next if messages.empty?
if attribute == :base
messages.each {|m| full_messages << m }
else
attr_name = @base.class.human_attribute_name(attribute.to_s)
prefix = attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ')
messages.each do |m|
full_messages << "#{prefix}#{m}"
end
end
end
full_messages
end
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
# translated attribute name and the value are available for interpolation.
#
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
#
# <ol>
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
# <li><tt>activerecord.errors.models.user.blank</tt></li>
# <li><tt>activerecord.errors.messages.blank</tt></li>
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
# </ol>
def generate_message(attribute, message = :invalid, options = {})
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
:"models.#{klass.name.underscore}.#{message}" ]
end
defaults << options.delete(:default)
defaults = defaults.compact.flatten << :"messages.#{message}"
key = defaults.shift
value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
options = { :default => defaults,
:model => @base.class.human_name,
:attribute => @base.class.human_attribute_name(attribute.to_s),
:value => value,
:scope => [:activerecord, :errors]
}.merge(options)
I18n.translate(key, options)
end
end
end

View File

@ -38,7 +38,7 @@ module ActiveModel
# end
#
# This usage applies to +validate_on_create+ and +validate_on_update as well+.
#
# Validates each attribute against a block.
#
# class Person < ActiveRecord::Base
@ -48,7 +48,7 @@ module ActiveModel
# end
#
# Options:
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@ -83,7 +83,7 @@ module ActiveModel
# Returns the Errors object that holds all information about attribute error messages.
def errors
@errors ||= Errors.new
@errors ||= Errors.new(self)
end
# Runs all the specified validations and returns true if no errors were added otherwise false.
@ -92,35 +92,52 @@ module ActiveModel
run_callbacks(:validate)
if responds_to?(:validate)
ActiveSupport::Deprecations.warn "Base#validate has been deprecated, please use Base.validate :method instead"
if respond_to?(:validate)
# ActiveSupport::Deprecation.warn "Base#validate has been deprecated, please use Base.validate :method instead"
validate
end
if new_record?
run_callbacks(:validate_on_create)
if responds_to?(:validate_on_create)
ActiveSupport::Deprecations.warn(
"Base#validate_on_create has been deprecated, please use Base.validate_on_create :method instead")
if respond_to?(:validate_on_create)
# ActiveSupport::Deprecation.warn "Base#validate_on_create has been deprecated, please use Base.validate_on_create :method instead"
validate_on_create
end
else
run_callbacks(:validate_on_update)
if responds_to?(:validate_on_update)
ActiveSupport::Deprecations.warn(
"Base#validate_on_update has been deprecated, please use Base.validate_on_update :method instead")
if respond_to?(:validate_on_update)
# ActiveSupport::Deprecation.warn "Base#validate_on_update has been deprecated, please use Base.validate_on_update :method instead"
validate_on_update
end
end
errors.empty?
end
# Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
def invalid?
!valid?
end
protected
# Overwrite this method for validation checks on all saves and use <tt>Errors.add(field, msg)</tt> for invalid attributes.
def validate
end
# Overwrite this method for validation checks used only on creation.
def validate_on_create
end
# Overwrite this method for validation checks used only on updates.
def validate_on_update
end
end
end
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
filename = File.basename(path)
require "active_model/validations/#{filename}"
end
end

View File

@ -8,16 +8,16 @@ module ActiveModel
# validates_acceptance_of :eula, :message => "must be abided"
# end
#
# If the database column does not exist, the <tt>:terms_of_service</tt> attribute is entirely virtual. This check is
# performed only if <tt>:terms_of_service</tt> is not +nil+ and by default on save.
# If the database column does not exist, the +terms_of_service+ attribute is entirely virtual. This check is
# performed only if +terms_of_service+ is not +nil+ and by default on save.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be accepted")
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. (default is +true+)
# * <tt>:message</tt> - A custom error message (default is: "must be accepted").
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is true).
# * <tt>:accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
# makes it easy to relate to an HTML checkbox. This should be set to +true+ if you are validating a database
# column, since the attribute is typecasted from "1" to +true+ before validation.
# column, since the attribute is typecast from "1" to +true+ before validation.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@ -25,19 +25,22 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_acceptance_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
configuration = { :on => :save, :allow_nil => true, :accept => "1" }
configuration.update(attr_names.extract_options!)
db_cols = begin
column_names
rescue ActiveRecord::StatementInvalid
rescue Exception # To ignore both statement and connection errors
[]
end
names = attr_names.reject { |name| db_cols.include?(name.to_s) }
attr_accessor(*names)
validates_each(attr_names,configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
unless value == configuration[:accept]
record.errors.add(attr_name, :accepted, :default => configuration[:message])
end
end
end
end

View File

@ -18,14 +18,14 @@ module ActiveModel
# validates_associated :book
# end
#
# ...this would specify a circular dependency and cause infinite recursion.
# this would specify a circular dependency and cause infinite recursion.
#
# NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
# is both present and guaranteed to be valid, you also need to use +validates_presence_of+.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@ -33,12 +33,13 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_associated(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) unless
(value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all?
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
end
end
end
end

View File

@ -21,8 +21,8 @@ module ActiveModel
# validates_presence_of :password_confirmation, :if => :password_changed?
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "doesn't match confirmation")
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:message</tt> - A custom error message (default is: "doesn't match confirmation").
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@ -30,13 +30,15 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
record.errors.add(attr_name, :confirmation, :default => configuration[:message])
end
end
end
end

View File

@ -6,14 +6,14 @@ module ActiveModel
# class Person < ActiveRecord::Base
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed"
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
# end
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't be part of
# * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved")
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+)
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't be part of.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@ -21,7 +21,7 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_exclusion_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
@ -29,7 +29,9 @@ module ActiveModel
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?)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value)
if enum.include?(value)
record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
end
end
end
end

View File

@ -13,11 +13,11 @@ module ActiveModel
# A regular expression must be provided or else an exception will be raised.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+)
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
# * <tt>:with</tt> - The regular expression used to validate the format with (note: must be supplied!)
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:with</tt> - The regular expression used to validate the format with (note: must be supplied!).
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@ -25,13 +25,15 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_format_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
configuration = { :on => :save, :with => nil }
configuration.update(attr_names.extract_options!)
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
unless value.to_s =~ configuration[:with]
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
end
end
end
end

View File

@ -6,14 +6,14 @@ module ActiveModel
# class Person < ActiveRecord::Base
# validates_inclusion_of :gender, :in => %w( m f )
# validates_inclusion_of :age, :in => 0..99
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list"
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
# end
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items
# * <tt>:message</tt> - Specifies a custom error message (default is: "is not included in the list")
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is null (default is: +false+)
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
# * <tt>:in</tt> - An enumerable object of available items.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is not included in the list").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
@ -21,7 +21,7 @@ module ActiveModel
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_inclusion_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
@ -29,7 +29,9 @@ module ActiveModel
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?)
validates_each(attr_names, configuration) do |record, attr_name, value|
record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value)
unless enum.include?(value)
record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
end
end
end
end

View File

@ -2,44 +2,46 @@ module ActiveModel
module Validations
module ClassMethods
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
#
# class Person < ActiveRecord::Base
# validates_length_of :first_name, :maximum => 30
# validates_length_of :last_name, :maximum => 30, :message => "less than %d if you don't mind"
# validates_length_of :first_name, :maximum=>30
# validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind"
# validates_length_of :fax, :in => 7..32, :allow_nil => true
# validates_length_of :phone, :in => 7..32, :allow_blank => true
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character"
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me."
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
# end
#
# Configuration options:
# * <tt>:minimum</tt> - The minimum size of the attribute
# * <tt>:maximum</tt> - The maximum size of the attribute
# * <tt>:is</tt> - The exact size of the attribute
# * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute.
# * <tt>:is</tt> - The exact size of the attribute.
# * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
# * <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 %d characters)")
# * <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
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
# * <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)").
# * <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.
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
# count words as in above example.)
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
def validates_length_of(*attrs)
# Merge given options with defaults.
options = {
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
:tokenizer => lambda {|value| value.split(//)}
}.merge(DEFAULT_VALIDATION_OPTIONS)
options.update(attrs.extract_options!.symbolize_keys)
@ -57,35 +59,33 @@ module ActiveModel
# Get range option and value.
option = range_options.first
option_value = options[range_options.first]
key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
custom_message = options[:message] || options[key]
case option
when :within, :in
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
when :within, :in
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
too_short = options[:too_short] % option_value.begin
too_long = options[:too_long] % option_value.end
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
record.errors.add(attr, too_short)
elsif value.size > option_value.end
record.errors.add(attr, too_long)
end
validates_each(attrs, options) do |record, attr, value|
value = options[:tokenizer].call(value) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin)
elsif value.size > option_value.end
record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end)
end
when :is, :minimum, :maximum
raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
end
when :is, :minimum, :maximum
raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
# Declare different validations per option.
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
# Declare different validations per option.
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
message = (options[:message] || options[message_options[option]]) % option_value
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
validates_each(attrs, options) do |record, attr, value|
value = options[:tokenizer].call(value) if value.kind_of?(String)
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
record.errors.add(attr, key, :default => custom_message, :count => option_value)
end
end
end
end

View File

@ -5,10 +5,9 @@ module ActiveModel
:equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
:odd => 'odd?', :even => 'even?' }.freeze
# Validates whether the value of the specified attribute is numeric by trying to convert it to
# a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>integer</tt> is true).
# a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
#
# class Person < ActiveRecord::Base
# validates_numericality_of :value, :on => :create
@ -50,15 +49,15 @@ module ActiveModel
if configuration[:only_integer]
unless raw_value.to_s =~ /\A[+-]?\d+\Z/
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
next
end
raw_value = raw_value.to_i
else
begin
raw_value = Kernel.Float(raw_value.to_s)
begin
raw_value = Kernel.Float(raw_value)
rescue ArgumentError, TypeError
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
next
end
end
@ -66,11 +65,13 @@ module ActiveModel
numericality_options.each do |option|
case option
when :odd, :even
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
end
else
message = configuration[:message] || ActiveRecord::Errors.default_error_messages[option]
message = message % configuration[option] if configuration[option]
record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
end
end
end
end

View File

@ -7,28 +7,26 @@ module ActiveModel
# validates_presence_of :first_name
# end
#
# The +first_name+ attribute must be in the object and it cannot be blank.
# The first_name attribute must be in the object and it cannot be blank.
#
# If you want to validate the presence of a boolean field (where the real values are +true+ and +false+),
# you will want to use
# If you want to validate the presence of a boolean field (where the real values are true and false),
# you will want to use <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
#
# validates_inclusion_of :field_name, :in => [true, false]
#
# This is due to the way Object#blank? handles boolean values:
#
# false.blank? # => true
# This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank")
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>message</tt> - A custom error message (default is: "can't be blank").
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
# <tt>:update</tt>).
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
# * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
#
def validates_presence_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
# can't use validates_each here, because it cannot cope with nonexistent attributes,

View File

@ -18,24 +18,84 @@ module ActiveModel
# When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
# attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
#
# Because this check is performed outside the database there is still a chance that duplicate values
# will be inserted in two parallel transactions. To guarantee against this you should create a
# unique index on the field. See +add_index+ for more information.
#
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken")
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+)
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the attribute is blank (default is: +false+)
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
#
# === Concurrency and integrity
#
# Using this validation method in conjunction with ActiveRecord::Base#save
# does not guarantee the absence of duplicate record insertions, because
# uniqueness checks on the application level are inherently prone to race
# conditions. For example, suppose that two users try to post a Comment at
# the same time, and a Comment's title must be unique. At the database-level,
# the actions performed by these users could be interleaved in the following manner:
#
# User 1 | User 2
# ------------------------------------+--------------------------------------
# # User 1 checks whether there's |
# # already a comment with the title |
# # 'My Post'. This is not the case. |
# SELECT * FROM comments |
# WHERE title = 'My Post' |
# |
# | # User 2 does the same thing and also
# | # infers that his title is unique.
# | SELECT * FROM comments
# | WHERE title = 'My Post'
# |
# # User 1 inserts his comment. |
# INSERT INTO comments |
# (title, content) VALUES |
# ('My Post', 'hi!') |
# |
# | # User 2 does the same thing.
# | INSERT INTO comments
# | (title, content) VALUES
# | ('My Post', 'hello!')
# |
# | # ^^^^^^
# | # Boom! We now have a duplicate
# | # title!
#
# This could even happen if you use transactions with the 'serializable'
# isolation level. There are several ways to get around this problem:
# - By locking the database table before validating, and unlocking it after
# saving. However, table locking is very expensive, and thus not
# recommended.
# - By locking a lock file before validating, and unlocking it after saving.
# This does not work if you've scaled your Rails application across
# multiple web servers (because they cannot share lock files, or cannot
# do that efficiently), and thus not recommended.
# - Creating a unique index on the field, by using
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
# rare case that a race condition occurs, the database will guarantee
# the field's uniqueness.
#
# When the database catches such a duplicate insertion,
# ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
# exception. You can either choose to let this error propagate (which
# will result in the default Rails exception page being shown), or you
# can catch it and restart the transaction (e.g. by telling the user
# that the title already exists, and asking him to re-enter the title).
# This technique is also known as optimistic concurrency control:
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
#
# Active Record currently provides no way to distinguish unique
# index constraint errors from other types of database errors, so you
# will have to parse the (database-specific) exception message to detect
# such a case.
def validates_uniqueness_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
configuration = { :case_sensitive => true }
configuration.update(attr_names.extract_options!)
validates_each(attr_names,configuration) do |record, attr_name, value|
@ -53,20 +113,31 @@ module ActiveModel
# class (which has a database table to query from).
finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?)
condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"
column = finder_class.columns_hash[attr_name.to_s]
if value.nil?
comparison_operator = "IS ?"
elsif column.text?
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
value = column.limit ? value.to_s[0, column.limit] : value.to_s
else
comparison_operator = "= ?"
end
sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
if value.nil? || (configuration[:case_sensitive] || !column.text?)
condition_sql = "#{sql_attribute} #{comparison_operator}"
condition_params = [value]
else
# sqlite has case sensitive SELECT query, while MySQL/Postgresql don't.
# Hence, this is needed only for sqlite.
condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
condition_params = [value.downcase]
condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
condition_params = [value.mb_chars.downcase]
end
if scope = configuration[:scope]
Array(scope).map do |scope_item|
scope_value = record.send(scope_item)
condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}"
condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
condition_params << scope_value
end
end
@ -76,26 +147,10 @@ module ActiveModel
condition_params << record.send(:id)
end
results = finder_class.with_exclusive_scope do
connection.select_all(
construct_finder_sql(
:select => attr_name,
:from => finder_class.quoted_table_name,
:conditions => [condition_sql, *condition_params]
)
)
end
unless results.length.zero?
found = true
# As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate
# column in ruby when case sensitive option
if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
found = results.any? { |a| a[attr_name.to_s] == value }
finder_class.with_exclusive_scope do
if finder_class.exists?([condition_sql, *condition_params])
record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
end
record.errors.add(attr_name, configuration[:message]) if found
end
end
end

View File

@ -31,6 +31,15 @@ rescue LoadError
end
end
begin
require 'active_model'
rescue LoadError
$:.unshift "#{File.dirname(__FILE__)}/../../activemodel/lib"
require 'active_model'
else
end
module ActiveRecord
# TODO: Review explicit loads to see if they will automatically be handled by the initilizer.
def self.load_all!

View File

@ -251,7 +251,7 @@ module ActiveRecord
unless association.marked_for_destruction?
association.errors.each do |attribute, message|
attribute = "#{reflection.name}_#{attribute}"
errors.add(attribute, message) unless errors.on(attribute)
errors[attribute] << message if errors[attribute].empty?
end
end
else

View File

@ -3136,7 +3136,7 @@ module ActiveRecord #:nodoc:
Base.class_eval do
extend QueryCache::ClassMethods
include Validations
include ::ActiveModel::Validations, Validations
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
include Dirty

File diff suppressed because it is too large Load Diff

View File

@ -713,8 +713,8 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@ship.pirate.catchphrase = ''
assert !@ship.valid?
assert !@ship.errors.on(:pirate_catchphrase).blank?
assert @ship.invalid?
assert @ship.errors[:pirate_catchphrase].any?
end
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid

View File

@ -39,8 +39,8 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
end
def test_default_error_messages_is_deprecated
assert_deprecated('ActiveRecord::Errors.default_error_messages') do
ActiveRecord::Errors.default_error_messages
assert_deprecated('Errors.default_error_messages') do
ActiveModel::Errors.default_error_messages
end
end
@ -70,7 +70,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
end
end
# ActiveRecord::Errors
# ActiveModel::Errors
def test_errors_generate_message_translates_custom_model_attribute_key
I18n.expects(:translate).with(
@ -161,7 +161,7 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase
end
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@topic.errors.instance_variable_set :@errors, { 'title' => ['empty'] }
@topic.errors.add('title', 'empty')
I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title')
@topic.errors.full_messages :locale => 'en'
end

View File

@ -75,7 +75,7 @@ class ValidationsTest < ActiveRecord::TestCase
r.title = "There's no content!"
assert !r.valid?
assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
assert_equal ["Empty"], r.errors["content"], "A reply without content should contain an error"
assert_equal 1, r.errors.count
end
@ -84,10 +84,10 @@ class ValidationsTest < ActiveRecord::TestCase
assert !r.valid?
assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
assert_equal ["Empty"], r.errors["title"], "A reply without title should contain an error"
assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
assert_equal ["Empty"], r.errors["content"], "A reply without content should contain an error"
assert_equal 2, r.errors.count
end
@ -97,7 +97,7 @@ class ValidationsTest < ActiveRecord::TestCase
r.title = "Wrong Create"
assert !r.valid?
assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
assert_equal "is Wrong Create", r.errors.on(:title), "A reply with a bad content should contain an error"
end
def test_error_on_update
@ -110,7 +110,7 @@ class ValidationsTest < ActiveRecord::TestCase
assert !r.save, "Second save should fail"
assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error"
assert_equal "is Wrong Update", r.errors.on(:title), "A reply with a bad content should contain an error"
end
def test_invalid_record_exception
@ -177,7 +177,7 @@ class ValidationsTest < ActiveRecord::TestCase
r.save
errors = []
r.errors.each { |attr, msg| errors << [attr, msg] }
r.errors.each {|attr, messages| errors << [attr.to_s, messages] }
assert errors.include?(["title", "Empty"])
assert errors.include?(["content", "Empty"])
@ -189,8 +189,7 @@ class ValidationsTest < ActiveRecord::TestCase
r.content = "Mismatch"
r.save
errors = []
r.errors.each_full { |error| errors << error }
errors = r.errors.to_a
assert_equal "Title is Wrong Create", errors[0]
assert_equal "Title is Content Mismatch", errors[1]
@ -234,8 +233,8 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.new("title" => "valid", "content" => "whatever")
assert !t.save
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors.on(:title)
assert_equal %w(gotcha gotcha), t.errors.on(:content)
assert_equal %w(gotcha gotcha), t.errors[:title]
assert_equal %w(gotcha gotcha), t.errors[:content]
end
def test_no_title_confirmation
@ -277,7 +276,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
assert !t.save
assert_equal "must be accepted", t.errors.on(:terms_of_service)
assert_equal ["must be accepted"], t.errors[:terms_of_service]
t.terms_of_service = "1"
assert t.save
@ -289,7 +288,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "We should be confirmed","eula" => "")
assert !t.save
assert_equal "must be abided", t.errors.on(:eula)
assert_equal ["must be abided"], t.errors[:eula]
t.eula = "1"
assert t.save
@ -300,7 +299,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
assert !t.save
assert_equal "must be accepted", t.errors.on(:terms_of_service)
assert_equal ["must be accepted"], t.errors[:terms_of_service]
t.terms_of_service = "I agree."
assert t.save
@ -328,14 +327,14 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create
assert !t.save
assert_equal "can't be blank", t.errors.on(:title)
assert_equal "can't be blank", t.errors.on(:content)
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["can't be blank"], t.errors[:content]
t.title = "something"
t.content = " "
assert !t.save
assert_equal "can't be blank", t.errors.on(:content)
assert_equal ["can't be blank"], t.errors[:content]
t.content = "like stuff"
@ -354,7 +353,7 @@ class ValidationsTest < ActiveRecord::TestCase
t2 = Topic.new("title" => "I'm unique!")
assert !t2.valid?, "Shouldn't be valid"
assert !t2.save, "Shouldn't save t2 as unique"
assert_equal "has already been taken", t2.errors.on(:title)
assert_equal ["has already been taken"], t2.errors[:title]
t2.title = "Now Im really also unique"
assert t2.save, "Should now save t2 as unique"
@ -441,15 +440,15 @@ class ValidationsTest < ActiveRecord::TestCase
t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
assert !t2.valid?, "Shouldn't be valid"
assert !t2.save, "Shouldn't save t2 as unique"
assert t2.errors.on(:title)
assert t2.errors.on(:parent_id)
assert_equal "has already been taken", t2.errors.on(:title)
assert t2.errors[:title].any?
assert t2.errors[:parent_id].any?
assert_equal ["has already been taken"], t2.errors[:title]
t2.title = "I'm truly UNIQUE!"
assert !t2.valid?, "Shouldn't be valid"
assert !t2.save, "Shouldn't save t2 as unique"
assert_nil t2.errors.on(:title)
assert t2.errors.on(:parent_id)
assert t2.errors[:title].empty?
assert t2.errors[:parent_id].any?
t2.parent_id = 4
assert t2.save, "Should now save t2 as unique"
@ -484,16 +483,16 @@ class ValidationsTest < ActiveRecord::TestCase
t2 = Topic.new("title" => "I'M UNIQUE!")
assert t2.valid?, "Should be valid"
assert t2.save, "Should save t2 as unique"
assert !t2.errors.on(:title)
assert !t2.errors.on(:parent_id)
assert_not_equal "has already been taken", t2.errors.on(:title)
assert t2.errors[:title].empty?
assert t2.errors[:parent_id].empty?
assert_not_equal ["has already been taken"], t2.errors[:title]
t3 = Topic.new("title" => "I'M uNiQUe!")
assert t3.valid?, "Should be valid"
assert t3.save, "Should save t2 as unique"
assert !t3.errors.on(:title)
assert !t3.errors.on(:parent_id)
assert_not_equal "has already been taken", t3.errors.on(:title)
assert t3.errors[:title].empty?
assert t3.errors[:parent_id].empty?
assert_not_equal ["has already been taken"], t3.errors[:title]
end
def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
@ -502,13 +501,13 @@ class ValidationsTest < ActiveRecord::TestCase
t2 = Topic.new('title' => 101)
assert !t2.valid?
assert t2.errors.on(:title)
assert t2.errors[:title]
end
def test_validate_uniqueness_with_non_standard_table_names
i1 = WarehouseThing.create(:value => 1000)
assert !i1.valid?, "i1 should not be valid"
assert i1.errors.on(:value), "Should not be empty"
assert i1.errors[:value].any?, "Should not be empty"
end
def test_validates_uniqueness_inside_with_scope
@ -546,26 +545,26 @@ class ValidationsTest < ActiveRecord::TestCase
# Should use validation from base class (which is abstract)
w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm")
assert !w2.valid?, "w2 shouldn't be valid"
assert w2.errors.on(:name), "Should have errors for name"
assert_equal "has already been taken", w2.errors.on(:name), "Should have uniqueness message for name"
assert w2.errors[:name].any?, "Should have errors for name"
assert_equal ["has already been taken"], w2.errors[:name], "Should have uniqueness message for name"
w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm")
assert !w3.valid?, "w3 shouldn't be valid"
assert w3.errors.on(:name), "Should have errors for name"
assert_equal "has already been taken", w3.errors.on(:name), "Should have uniqueness message for name"
assert w3.errors[:name].any?, "Should have errors for name"
assert_equal ["has already been taken"], w3.errors[:name], "Should have uniqueness message for name"
w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm")
assert w4.valid?, "Saving w4"
w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre")
assert !w5.valid?, "w5 shouldn't be valid"
assert w5.errors.on(:name), "Should have errors for name"
assert_equal "has already been taken", w5.errors.on(:name), "Should have uniqueness message for name"
assert w5.errors[:name].any?, "Should have errors for name"
assert_equal ["has already been taken"], w5.errors[:name], "Should have uniqueness message for name"
w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm")
assert !w6.valid?, "w6 shouldn't be valid"
assert w6.errors.on(:city), "Should have errors for city"
assert_equal "has already been taken", w6.errors.on(:city), "Should have uniqueness message for city"
assert w6.errors[:city].any?, "Should have errors for city"
assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city"
end
def test_validate_format
@ -574,13 +573,13 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
assert !t.valid?, "Shouldn't be valid"
assert !t.save, "Shouldn't save because it's invalid"
assert_equal "is bad data", t.errors.on(:title)
assert_nil t.errors.on(:content)
assert_equal ["is bad data"], t.errors[:title]
assert t.errors[:content].empty?
t.title = "Validation macros rule!"
assert t.save
assert_nil t.errors.on(:title)
assert t.errors[:title].empty?
assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
end
@ -600,8 +599,8 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "72x", "content" => "6789")
assert !t.valid?, "Shouldn't be valid"
assert !t.save, "Shouldn't save because it's invalid"
assert_equal "is bad data", t.errors.on(:title)
assert_nil t.errors.on(:content)
assert_equal ["is bad data"], t.errors[:title]
assert t.errors[:content].empty?
t.title = "-11"
assert !t.valid?, "Shouldn't be valid"
@ -618,7 +617,7 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "1"
assert t.save
assert_nil t.errors.on(:title)
assert t.errors[:title].empty?
end
def test_validate_format_with_formatted_message
@ -639,7 +638,7 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "uhoh"
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is not included in the list", t.errors["title"]
assert_equal "is not included in the list", t.errors.on(:title)
assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
@ -692,7 +691,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "uhoh", "content" => "abc")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "option uhoh is not in the list", t.errors["title"]
assert_equal "option uhoh is not in the list", t.errors.on(:title)
end
def test_numericality_with_allow_nil_and_getter_method
@ -719,7 +718,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "monkey")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "option monkey is restricted", t.errors["title"]
assert_equal "option monkey is restricted", t.errors.on(:title)
end
def test_validates_length_of_using_minimum
@ -731,17 +730,17 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "not"
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
assert_equal "is too short (minimum is 5 characters)", t.errors.on(:title)
t.title = ""
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
assert_equal "is too short (minimum is 5 characters)", t.errors.on(:title)
t.title = nil
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
def test_optionally_validates_length_of_using_minimum
@ -763,7 +762,7 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "notvalid"
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
assert_equal "is too long (maximum is 5 characters)", t.errors.on(:title)
t.title = ""
assert t.valid?
@ -817,7 +816,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
assert !t.save
assert t.errors.on(:title)
assert_equal "my string is too long: 10", t.errors[:title]
assert_equal "my string is too long: 10", t.errors.on(:title)
t.title = "butthisis"
assert t.save
@ -842,7 +841,7 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "not"
assert !t.save
assert t.errors.on(:title)
assert_equal "my string is too short: 5", t.errors[:title]
assert_equal "my string is too short: 5", t.errors.on(:title)
t.title = "valid"
t.content = "andthisistoolong"
@ -862,7 +861,7 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "notvalid"
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
assert_equal "is the wrong length (should be 5 characters)", t.errors.on(:title)
t.title = ""
assert !t.valid?
@ -896,13 +895,13 @@ class ValidationsTest < ActiveRecord::TestCase
def test_validates_length_with_globally_modified_error_message
ActiveSupport::Deprecation.silence do
ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre {{count}}'
ActiveModel::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre {{count}}'
end
Topic.validates_length_of :title, :minimum => 10
t = Topic.create(:title => 'too short')
assert !t.valid?
assert_equal 'tu est trops petit hombre 10', t.errors['title']
assert_equal 'tu est trops petit hombre 10', t.errors.on(:title)
end
def test_validates_size_of_association
@ -948,7 +947,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "uhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "boo 5", t.errors["title"]
assert_equal "boo 5", t.errors.on(:title)
end
def test_validates_length_of_custom_errors_for_minimum_with_too_short
@ -956,7 +955,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "uhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert_equal "hoo 5", t.errors.on(:title)
end
def test_validates_length_of_custom_errors_for_maximum_with_message
@ -964,44 +963,44 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "boo 5", t.errors["title"]
assert_equal ["boo 5"], t.errors[:title]
end
def test_validates_length_of_custom_errors_for_in
Topic.validates_length_of(:title, :in => 10..20, :message => "hoo {{count}}")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 10", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 10"], t.errors["title"]
t = Topic.create("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 20", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 20"], t.errors["title"]
end
def test_validates_length_of_custom_errors_for_maximum_with_too_long
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_validates_length_of_custom_errors_for_is_with_message
Topic.validates_length_of( :title, :is=>5, :message=>"boo {{count}}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "boo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["boo 5"], t.errors["title"]
end
def test_validates_length_of_custom_errors_for_is_with_wrong_length
Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo {{count}}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_validates_length_of_using_minimum_utf8
@ -1013,8 +1012,8 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "一二三四"
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
end
@ -1027,8 +1026,8 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "一二34五六"
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"]
end
end
@ -1038,8 +1037,8 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.new("title" => "一二", "content" => "12三四五六七")
assert !t.valid?
assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
t.title = "一二三"
t.content = "12三"
assert t.valid?
@ -1067,8 +1066,8 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
assert !t.save
assert t.errors.on(:title)
assert_equal "長すぎます: 10", t.errors[:title]
assert t.errors[:title].any?
assert_equal "長すぎます: 10", t.errors[:title].first
t.title = "一二三四五六七八九"
assert t.save
@ -1090,16 +1089,16 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "一二三4", "content" => "whatever")
assert !t.save
assert t.errors.on(:title)
assert t.errors[:title].any?
t.title = "1二三4"
assert !t.save
assert t.errors.on(:title)
assert_equal "短すぎます: 5", t.errors[:title]
assert t.errors[:title].any?
assert_equal "短すぎます: 5", t.errors.on(:title)
t.title = "一二三四五六七八九十A"
assert !t.save
assert t.errors.on(:title)
assert t.errors[:title].any?
t.title = "一二345"
assert t.save
@ -1115,8 +1114,8 @@ class ValidationsTest < ActiveRecord::TestCase
t.title = "一二345六"
assert !t.valid?
assert t.errors.on(:title)
assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
end
end
@ -1128,8 +1127,8 @@ class ValidationsTest < ActiveRecord::TestCase
t.content = "not long enough"
assert !t.valid?
assert t.errors.on(:content)
assert_equal "Your essay must be at least 5 words.", t.errors[:content]
assert t.errors[:content].any?
assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
end
def test_validates_size_of_association_utf8
@ -1138,7 +1137,7 @@ class ValidationsTest < ActiveRecord::TestCase
assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
o = Owner.new('name' => 'あいうえおかきくけこ')
assert !o.save
assert o.errors.on(:pets)
assert o.errors[:pets].any?
o.pets.build('name' => 'あいうえおかきくけこ')
assert o.valid?
end
@ -1150,7 +1149,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
assert !t.valid?
assert t.errors.on(:replies)
assert t.errors[:replies].any?
assert_equal 1, r.errors.count # make sure all associated objects have been validated
assert_equal 0, r2.errors.count
assert_equal 1, r3.errors.count
@ -1166,7 +1165,7 @@ class ValidationsTest < ActiveRecord::TestCase
r = Reply.new("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
assert !r.valid?
assert r.errors.on(:topic)
assert r.errors[:topic].any?
r.topic.content = "non-empty"
assert r.valid?
end
@ -1176,8 +1175,8 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
t = Topic.create("title" => "Title", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "will never be valid", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["will never be valid"], t.errors["title"]
end
def test_invalid_validator
@ -1198,7 +1197,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.salary = "0"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
assert_equal "This string contains 'single' and \"double\" quotes", d.errors[:salary].last
end
end
@ -1209,7 +1208,7 @@ class ValidationsTest < ActiveRecord::TestCase
d.name = "John"
d.name_confirmation = "Johnny"
assert !d.valid?
assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
assert_equal ["confirm 'single' and \"double\" quotes"], d.errors[:name]
end
end
@ -1219,7 +1218,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.name = d.name_confirmation = "John 32"
assert !d.valid?
assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
assert_equal ["format 'single' and \"double\" quotes"], d.errors[:name]
end
end
@ -1229,7 +1228,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.salary = "90,000"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
assert_equal "This string contains 'single' and \"double\" quotes", d.errors[:salary].last
end
end
@ -1239,7 +1238,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.name = "Jeffrey"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
end
end
@ -1249,7 +1248,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
end
end
@ -1259,7 +1258,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
end
end
@ -1269,7 +1268,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.name = "Joe"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:non_existent]
end
end
@ -1279,7 +1278,7 @@ class ValidationsTest < ActiveRecord::TestCase
d = Developer.new
d.name = "David"
assert !d.valid?
assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name)
assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name]
end
end
@ -1290,7 +1289,7 @@ class ValidationsTest < ActiveRecord::TestCase
r = Reply.create("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
assert !r.valid?
assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic)
assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic]
end
end
@ -1299,8 +1298,8 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_unless_validation_using_method_true
@ -1308,7 +1307,7 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
assert !t.errors[:title].any?
end
def test_if_validation_using_method_false
@ -1316,7 +1315,7 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true_but_its_not )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
assert t.errors[:title].empty?
end
def test_unless_validation_using_method_false
@ -1324,8 +1323,8 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true_but_its_not )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_if_validation_using_string_true
@ -1333,8 +1332,8 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "a = 1; a == 1" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_unless_validation_using_string_true
@ -1342,7 +1341,7 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "a = 1; a == 1" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
assert t.errors[:title].empty?
end
def test_if_validation_using_string_false
@ -1350,7 +1349,7 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "false")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
assert t.errors[:title].empty?
end
def test_unless_validation_using_string_false
@ -1358,8 +1357,8 @@ class ValidationsTest < ActiveRecord::TestCase
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "false")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_if_validation_using_block_true
@ -1368,8 +1367,8 @@ class ValidationsTest < ActiveRecord::TestCase
:if => Proc.new { |r| r.content.size > 4 } )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_unless_validation_using_block_true
@ -1378,7 +1377,7 @@ class ValidationsTest < ActiveRecord::TestCase
:unless => Proc.new { |r| r.content.size > 4 } )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
assert t.errors[:title].empty?
end
def test_if_validation_using_block_false
@ -1387,7 +1386,7 @@ class ValidationsTest < ActiveRecord::TestCase
:if => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
assert t.errors[:title].empty?
end
def test_unless_validation_using_block_false
@ -1396,8 +1395,8 @@ class ValidationsTest < ActiveRecord::TestCase
:unless => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_validates_associated_missing
@ -1405,7 +1404,7 @@ class ValidationsTest < ActiveRecord::TestCase
Reply.validates_presence_of(:topic)
r = Reply.create("title" => "A reply", "content" => "with content!")
assert !r.valid?
assert r.errors.on(:topic)
assert r.errors[:topic].any?
r.topic = Topic.find :first
assert r.valid?
@ -1427,7 +1426,7 @@ class ValidationsTest < ActiveRecord::TestCase
t = Topic.new("title" => "")
assert !t.valid?
assert_equal "can't be blank", t.errors.on("title").first
assert_equal "can't be blank", t.errors["title"].first
end
def test_invalid_should_be_the_opposite_of_valid
@ -1481,7 +1480,6 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
def test_default_validates_numericality_of
Topic.validates_numericality_of :approved
invalid!(NIL + BLANK + JUNK)
valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
end
@ -1578,11 +1576,11 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
end
private
def invalid!(values, error=nil)
def invalid!(values, error = nil)
with_each_topic_approved_value(values) do |topic, value|
assert !topic.valid?, "#{value.inspect} not rejected as a number"
assert topic.errors.on(:approved)
assert_equal error, topic.errors.on(:approved) if error
assert topic.errors[:approved].any?, "FAILED for #{value.inspect}"
assert_equal error, topic.errors[:approved].first if error
end
end
@ -1593,7 +1591,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
end
def with_each_topic_approved_value(values)
topic = Topic.new("title" => "numeric test", "content" => "whatever")
topic = Topic.new(:title => "numeric test", :content => "whatever")
values.each do |value|
topic.approved = value
yield topic, value

View File

@ -12,25 +12,25 @@ class Reply < Topic
attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
def validate
errors.add("title", "Empty") unless attribute_present? "title"
errors[:title] << "Empty" unless attribute_present?("title")
end
def errors_on_empty_content
errors.add("content", "Empty") unless attribute_present? "content"
errors[:content] << "Empty" unless attribute_present?("content")
end
def validate_on_create
if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
errors.add("title", "is Content Mismatch")
errors[:title] << "is Content Mismatch"
end
end
def title_is_wrong_create
errors.add("title", "is Wrong Create") if attribute_present?("title") && title == "Wrong Create"
errors[:title] << "is Wrong Create" if attribute_present?("title") && title == "Wrong Create"
end
def validate_on_update
errors.add("title", "is Wrong Update") if attribute_present?("title") && title == "Wrong Update"
errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update"
end
end