diff options
Diffstat (limited to 'activemodel')
30 files changed, 564 insertions, 485 deletions
diff --git a/activemodel/README b/activemodel/README index 3945a6da06..6f162ef408 100644 --- a/activemodel/README +++ b/activemodel/README @@ -177,7 +177,8 @@ functionality from the following modules: end - person = Person.new(:first_name => 'zoolander') + person = Person.new + person.first_name = 'zoolander' person.valid? #=> false {Learn more}[link:classes/ActiveModel/Validations.html] diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 1dba664539..1dba664539 100755..100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index a7690ba5b9..817640b178 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -46,7 +46,7 @@ module ActiveModel # end # end # - # Notice that whenever you include ActiveModel::AtributeMethods in your class, + # Notice that whenever you include ActiveModel::AttributeMethods in your class, # it requires you to implement a <tt>attributes</tt> methods which returns a hash # with each attribute name in your model as hash key and the attribute value as # hash value. diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 257644b3fa..e7aad17021 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -2,11 +2,11 @@ require 'active_support/core_ext/array/wrap' require 'active_support/callbacks' module ActiveModel - # == Active Model Call Backs + # == Active Model Callbacks # # Provides an interface for any class to have Active Record like callbacks. # - # Like the Active Record methods, the call back chain is aborted as soon as + # Like the Active Record methods, the callback chain is aborted as soon as # one of the methods in the chain returns false. # # First, extend ActiveModel::Callbacks from the class you are creating: @@ -15,13 +15,13 @@ module ActiveModel # extend ActiveModel::Callbacks # end # - # Then define a list of methods that you want call backs attached to: + # Then define a list of methods that you want callbacks attached to: # # define_model_callbacks :create, :update # # This will provide all three standard callbacks (before, around and after) around # both the :create and :update methods. To implement, you need to wrap the methods - # you want call backs on in a block so that the call backs get a chance to fire: + # you want callbacks on in a block so that the callbacks get a chance to fire: # # def create # _run_create_callbacks do @@ -61,7 +61,7 @@ module ActiveModel # # define_model_callbacks :initializer, :only => :after # - # Note, the <tt>:only => <type></tt> hash will apply to all call backs defined on + # Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on # that method call. To get around this you can call the define_model_callbacks # method as many times as you need. # @@ -72,8 +72,8 @@ module ActiveModel # Would create +after_create+, +before_update+ and +around_destroy+ methods only. # # You can pass in a class to before_<type>, after_<type> and around_<type>, in which - # case the call back will call that class's <action>_<type> method passing the object - # that the call back is being called on. + # case the callback will call that class's <action>_<type> method passing the object + # that the callback is being called on. # # class MyModel # extend ActiveModel::Callbacks @@ -84,7 +84,7 @@ module ActiveModel # # class AnotherClass # def self.before_create( obj ) - # # obj is the MyModel instance that the call back is being called on + # # obj is the MyModel instance that the callback is being called on # end # end # diff --git a/activemodel/lib/active_model/deprecated_error_methods.rb b/activemodel/lib/active_model/deprecated_error_methods.rb index dd8050c549..adc50773d9 100644 --- a/activemodel/lib/active_model/deprecated_error_methods.rb +++ b/activemodel/lib/active_model/deprecated_error_methods.rb @@ -16,7 +16,7 @@ module ActiveModel 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#add(:base, msg) instead" self[:base] << msg end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index a82ce1bee0..4c80863e3a 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -4,7 +4,7 @@ require 'active_support/hash_with_indifferent_access' require 'active_support/core_ext/object/duplicable' module ActiveModel - # == Active Model Call Backs + # == Active Model Dirty # # Provides a way to track changes in your object in the same way as # Active Record does. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index f4c7400621..d42fc5291d 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/hash/reverse_merge' require 'active_support/ordered_hash' module ActiveModel @@ -61,6 +62,8 @@ module ActiveModel class Errors < ActiveSupport::OrderedHash include DeprecatedErrorMethods + CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank] + # Pass in the instance of the object that is using the errors object. # # class Person @@ -162,15 +165,12 @@ module ActiveModel # # <error>name must be specified</error> # # </errors> def to_xml(options={}) - require 'builder' unless defined? ::Builder - options[:root] ||= "errors" - options[:indent] ||= 2 - options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) - - options[:builder].instruct! unless options.delete(:skip_instruct) - options[:builder].errors do |e| - to_a.each { |error| e.error(error) } - end + to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true) + end + + # Returns an array as JSON representation for this object. + def as_json(options=nil) + to_a end # Adds +message+ to the error messages on +attribute+, which will be returned on a call to @@ -182,25 +182,44 @@ module ActiveModel # If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error. def add(attribute, message = nil, options = {}) message ||= :invalid - message = generate_message(attribute, message, options) if message.is_a?(Symbol) - message = message.call if message.is_a?(Proc) + + if message.is_a?(Symbol) + message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) + elsif message.is_a?(Proc) + message = message.call + end + 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) + def add_on_empty(attributes, options = {}) + if options && !options.is_a?(Hash) + options = { :message => options } + ActiveSupport::Deprecation.warn \ + "ActiveModel::Errors#add_on_empty(attributes, custom_message) has been deprecated.\n" + + "Instead of passing a custom_message pass an options Hash { :message => custom_message }." + end + [attributes].flatten.each do |attribute| value = @base.send(:read_attribute_for_validation, attribute) is_empty = value.respond_to?(:empty?) ? value.empty? : false - add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty + add(attribute, :empty, options) if 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) + def add_on_blank(attributes, options = {}) + if options && !options.is_a?(Hash) + options = { :message => options } + ActiveSupport::Deprecation.warn \ + "ActiveModel::Errors#add_on_blank(attributes, custom_message) has been deprecated.\n" + + "Instead of passing a custom_message pass an options Hash { :message => custom_message }." + end + [attributes].flatten.each do |attribute| value = @base.send(:read_attribute_for_validation, attribute) - add(attribute, :blank, :default => custom_message) if value.blank? + add(attribute, :blank, options) if value.blank? end end @@ -246,7 +265,7 @@ module ActiveModel # (e.g. <tt>activemodel.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 + # When using inheritance 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+, @@ -262,24 +281,31 @@ module ActiveModel # <li><tt>errors.attributes.title.blank</tt></li> # <li><tt>errors.messages.blank</tt></li> # </ol> - def generate_message(attribute, message = :invalid, options = {}) - message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) + def generate_message(attribute, type = :invalid, options = {}) + type = options.delete(:message) if options[:message].is_a?(Symbol) + + if options[:default] + ActiveSupport::Deprecation.warn \ + "ActiveModel::Errors#generate_message(attributes, custom_message) has been deprecated.\n" + + "Use ActiveModel::Errors#generate_message(attributes, :message => 'your message') instead." + options[:message] = options.delete(:default) + end defaults = @base.class.lookup_ancestors.map do |klass| - [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{message}", - :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{message}" ] + [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{type}", + :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{type}" ] end - defaults << options.delete(:default) - defaults << :"#{@base.class.i18n_scope}.errors.messages.#{message}" - defaults << :"errors.attributes.#{attribute}.#{message}" - defaults << :"errors.messages.#{message}" + defaults << options.delete(:message) + defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" + defaults << :"errors.attributes.#{attribute}.#{type}" + defaults << :"errors.messages.#{type}" defaults.compact! defaults.flatten! key = defaults.shift - value = @base.send(:read_attribute_for_validation, attribute) + value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil) options = { :default => defaults, diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index a5977bf3cc..fe650053d9 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -56,7 +56,7 @@ module ActiveModel # Returns a boolean that specifies whether the object has been persisted yet. # This is used when calculating the URL for an object. If the object is # not persisted, a form for that object, for instance, will be POSTed to the - # collection. If it is persisted, a form for the object will put PUTed to the + # collection. If it is persisted, a form for the object will be PUT to the # URL for the object. def test_persisted? assert model.respond_to?(:persisted?), "The model should respond to persisted?" @@ -65,7 +65,7 @@ module ActiveModel # == Naming # - # Model.model_name must returns a string with some convenience methods as + # Model.model_name must return a string with some convenience methods as # :human and :partial_path. Check ActiveModel::Naming for more information. # def test_model_naming diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 14f8bf72dc..d0f36ce3b1 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -2,7 +2,6 @@ require 'singleton' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/string/conversions' module ActiveModel module Observing diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 3228cfed9a..0554677296 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -13,7 +13,7 @@ module ActiveModel # extend ActiveModel::Translation # end # - # TranslatedPerson.human_attribute_name('my_attribue') + # TranslatedPerson.human_attribute_name('my_attribute') # #=> "My attribute" # # This also provides the required class methods for hooking into the diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 57487cf75a..5779ba3b29 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/keys' require 'active_model/errors' +require 'active_model/validations/callbacks' module ActiveModel @@ -91,7 +92,7 @@ module ActiveModel end # Adds a validation method or block to the class. This is useful when - # overriding the +validate+ instance method becomes too unwieldly and + # overriding the +validate+ instance method becomes too unwieldy and # you're looking for more descriptive declaration of your validations. # # This can be done with a symbol pointing to a method: @@ -164,8 +165,7 @@ module ActiveModel def valid?(context = nil) current_context, self.validation_context = validation_context, context errors.clear - _run_validate_callbacks - errors.empty? + run_validations! ensure self.validation_context = current_context end @@ -176,7 +176,7 @@ module ActiveModel !valid?(context) end - # Hook method defining how an attribute value should be retieved. By default + # Hook method defining how an attribute value should be retrieved. By default # this is assumed to be an instance named after the attribute. Override this # method in subclasses should you need to retrieve the value for a given # attribute differently: @@ -194,6 +194,13 @@ module ActiveModel # end # alias :read_attribute_for_validation :send + + protected + + def run_validations! + _run_validate_callbacks + errors.empty? + end end end diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 26b12b504b..99b8966def 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -1,4 +1,6 @@ module ActiveModel + + # == Active Model Acceptance Validator module Validations class AcceptanceValidator < EachValidator def initialize(options) @@ -7,10 +9,10 @@ module ActiveModel def validate_each(record, attribute, value) unless value == options[:accept] - record.errors.add(attribute, :accepted, :default => options[:message]) + record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil)) end end - + def setup(klass) # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility # as instance_methods returns symbols unlike 1.8 which returns strings. @@ -22,29 +24,42 @@ module ActiveModel end module HelperMethods - # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example: + # Encapsulates the pattern of wanting to validate the acceptance of a + # terms of service check box (or similar agreement). Example: # # class Person < ActiveRecord::Base # validates_acceptance_of :terms_of_service # validates_acceptance_of :eula, :message => "must be abided" # end # - # 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. + # 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>: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 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. - # * <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: "must be + # accepted"). + # * <tt>:on</tt> - Specifies when this validation is active (default is + # <tt>:save</tt>, other options are <tt>:create</tt> and + # <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 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. + # * <tt>:unless</tt> - Specifies a method, proc or string to call to + # determine if the validation should not occur (for example, + # <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) validates_with AcceptanceValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb new file mode 100644 index 0000000000..afd65d3dd5 --- /dev/null +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -0,0 +1,57 @@ +require 'active_support/callbacks' + +module ActiveModel + module Validations + module Callbacks + # == Active Model Validation callbacks + # + # Provides an interface for any class to have <tt>before_validation</tt> and + # <tt>after_validation</tt> callbacks. + # + # First, extend ActiveModel::Callbacks from the class you are creating: + # + # class MyModel + # include ActiveModel::Validations::Callbacks + # + # before_validation :do_stuff_before_validation + # after_validation :do_tuff_after_validation + # end + # + # Like other before_* callbacks if <tt>before_validation</tt> returns false + # then <tt>valid?</tt> will not be called. + extend ActiveSupport::Concern + + included do + include ActiveSupport::Callbacks + define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name] + end + + module ClassMethods + def before_validation(*args, &block) + options = args.last + if options.is_a?(Hash) && options[:on] + options[:if] = Array.wrap(options[:if]) + options[:if] << "self.validation_context == :#{options[:on]}" + end + set_callback(:validation, :before, *args, &block) + end + + def after_validation(*args, &block) + options = args.extract_options! + options[:prepend] = true + options[:if] = Array.wrap(options[:if]) + options[:if] << "!halted && value != false" + options[:if] << "self.validation_context == :#{options[:on]}" if options[:on] + set_callback(:validation, :after, *(args << options), &block) + end + end + + protected + + # Overwrite run validations to include callbacks. + def run_validations! + _run_validation_callbacks { super } + end + end + end +end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index 51445343f2..3a80893866 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -1,45 +1,59 @@ module ActiveModel + + # == Active Model Confirmation Validator module Validations class ConfirmationValidator < EachValidator def validate_each(record, attribute, value) confirmed = record.send(:"#{attribute}_confirmation") return if confirmed.nil? || value == confirmed - record.errors.add(attribute, :confirmation, :default => options[:message]) + record.errors.add(attribute, :confirmation, options) end - + def setup(klass) - klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" }) + klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" }) end end module HelperMethods - # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example: + # Encapsulates the pattern of wanting to validate a password or email + # address field with a confirmation. For example: # # Model: # class Person < ActiveRecord::Base # validates_confirmation_of :user_name, :password - # validates_confirmation_of :email_address, :message => "should match confirmation" + # validates_confirmation_of :email_address, + # :message => "should match confirmation" # end # # View: # <%= password_field "person", "password" %> # <%= password_field "person", "password_confirmation" %> # - # The added +password_confirmation+ attribute is virtual; it exists only as an in-memory attribute for validating the password. - # To achieve this, the validation adds accessors to the model for the confirmation attribute. NOTE: This check is performed - # only if +password_confirmation+ is not +nil+, and by default only on save. To require confirmation, make sure to add a presence - # check for the confirmation attribute: + # The added +password_confirmation+ attribute is virtual; it exists only + # as an in-memory attribute for validating the password. To achieve this, + # the validation adds accessors to the model for the confirmation + # attribute. + # + # NOTE: This check is performed only if +password_confirmation+ is not + # +nil+, and by default only on save. To require confirmation, make sure + # to add a presence check for the confirmation attribute: # # 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>: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 + # * <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. + # * <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_confirmation_of(*attr_names) validates_with ConfirmationValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index 2ee78f5dd2..4138892786 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -1,4 +1,6 @@ module ActiveModel + + # == Active Model Exclusion Validator module Validations class ExclusionValidator < EachValidator def check_validity! @@ -7,8 +9,9 @@ module ActiveModel end def validate_each(record, attribute, value) - return unless options[:in].include?(value) - record.errors.add(attribute, :exclusion, :default => options[:message], :value => value) + if options[:in].include?(value) + record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value)) + end end end diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index c34c860d4d..104f403492 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -1,14 +1,16 @@ module ActiveModel + + # == Active Model Format Validator module Validations class FormatValidator < EachValidator def validate_each(record, attribute, value) if options[:with] && value.to_s !~ options[:with] - record.errors.add(attribute, :invalid, :default => options[:message], :value => value) + record.errors.add(attribute, :invalid, options.except(:with).merge!(:value => value)) elsif options[:without] && value.to_s =~ options[:without] - record.errors.add(attribute, :invalid, :default => options[:message], :value => value) + record.errors.add(attribute, :invalid, options.except(:without).merge!(:value => value)) end end - + def check_validity! unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" raise ArgumentError, "Either :with or :without must be supplied (but not both)" diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 446646d247..049b093618 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -1,4 +1,6 @@ module ActiveModel + + # == Active Model Inclusion Validator module Validations class InclusionValidator < EachValidator def check_validity! @@ -7,8 +9,9 @@ module ActiveModel end def validate_each(record, attribute, value) - return if options[:in].include?(value) - record.errors.add(attribute, :inclusion, :default => options[:message], :value => value) + unless options[:in].include?(value) + record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value)) + end end end diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index d7218f4f52..c8a77ad666 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -1,10 +1,13 @@ module ActiveModel + + # == Active Model Length Validator module Validations class LengthValidator < EachValidator MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze DEFAULT_TOKENIZER = lambda { |value| value.split(//) } + RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long] def initialize(options) if range = (options.delete(:in) || options.delete(:within)) @@ -37,7 +40,8 @@ module ActiveModel CHECKS.each do |key, validity_check| next unless check_value = options[key] - custom_message = options[:message] || options[MESSAGES[key]] + default_message = options[MESSAGES[key]] + options[:message] ||= default_message if default_message valid_value = if key == :maximum value.nil? || value.size.send(validity_check, check_value) @@ -46,7 +50,9 @@ module ActiveModel end next if valid_value - record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value) + + record.errors.add(attribute, MESSAGES[key], + options.except(*RESERVED_OPTIONS).merge!(:count => check_value)) end end end diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 062b4cd17f..b6aff7aa6b 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -1,10 +1,14 @@ module ActiveModel + + # == Active Model Numericality Validator 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 + RESERVED_OPTIONS = CHECKS.keys + [:only_integer] + def initialize(options) super(options.reverse_merge(:only_integer => false, :allow_nil => false)) end @@ -26,13 +30,13 @@ module ActiveModel return if options[:allow_nil] && raw_value.nil? unless value = parse_raw_value_as_a_number(raw_value) - record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message]) + record.errors.add(attr_name, :not_a_number, filtered_options(raw_value)) return end if options[:only_integer] unless value = parse_raw_value_as_an_integer(raw_value) - record.errors.add(attr_name, :not_an_integer, :value => raw_value, :default => options[:message]) + record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value)) return end end @@ -41,14 +45,14 @@ module ActiveModel case option when :odd, :even unless value.to_i.send(CHECKS[option]) - record.errors.add(attr_name, option, :value => value, :default => options[:message]) + record.errors.add(attr_name, option, filtered_options(value)) end else 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) + record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value)) end end end @@ -73,6 +77,9 @@ module ActiveModel raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/ end + def filtered_options(value) + options.except(*RESERVED_OPTIONS).merge!(:value => value) + end end module HelperMethods diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index b319f4834b..28c4640b17 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -1,10 +1,12 @@ require 'active_support/core_ext/object/blank' module ActiveModel + + # == Active Model Presence Validator module Validations class PresenceValidator < EachValidator def validate(record) - record.errors.add_on_blank(attributes, options[:message]) + record.errors.add_on_blank(attributes, options) end end diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 90b244228a..0674640925 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/hash/slice' module ActiveModel + + # == Active Model validates method module Validations module ClassMethods # This method is a shortcut to all default validators and any custom @@ -20,7 +22,7 @@ module ActiveModel # validates :username, :presence => true # validates :username, :uniqueness => true # - # The power of the +validates+ method comes when using cusom validators + # The power of the +validates+ method comes when using custom validators # and default validators in one call for a given attribute e.g. # # class EmailValidator < ActiveModel::EachValidator diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 6dbde5bfad..200efd4eb5 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -9,7 +9,6 @@ module ActiveModel end module ClassMethods - # Passes the record off to the class or classes specified and allows them # to add errors based on more complex conditions. # diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index dd9e39843e..689c617177 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -109,7 +109,7 @@ module ActiveModel #:nodoc: @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous? end - # Accepts options that will be made availible through the +options+ reader. + # Accepts options that will be made available through the +options+ reader. def initialize(options) @options = options end @@ -155,7 +155,7 @@ module ActiveModel #:nodoc: end end - #Â Override this method in subclasses with the validation logic, adding + # Override this method in subclasses with the validation logic, adding # errors to the records +errors+ array where necessary. def validate_each(record, attribute, value) raise NotImplementedError diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index a81584bbad..a32f11484a 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -6,6 +6,7 @@ $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) require 'config' require 'active_model' require 'active_support/core_ext/string/access' +require 'active_support/core_ext/hash/except' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb new file mode 100644 index 0000000000..1cf09758f9 --- /dev/null +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -0,0 +1,78 @@ +# encoding: utf-8 +require 'cases/helper' + +class Dog + include ActiveModel::Validations + include ActiveModel::Validations::Callbacks + + attr_accessor :name + attr_writer :history + + def history + @history ||= [] + end +end + +class DogWithMethodCallbacks < Dog + before_validation :set_before_validation_marker + after_validation :set_after_validation_marker + + def set_before_validation_marker; self.history << 'before_validation_marker'; end + def set_after_validation_marker; self.history << 'after_validation_marker' ; end +end + +class DogValidtorsAreProc < Dog + before_validation { self.history << 'before_validation_marker' } + after_validation { self.history << 'after_validation_marker' } +end + +class DogWithTwoValidators < Dog + before_validation { self.history << 'before_validation_marker1' } + before_validation { self.history << 'before_validation_marker2' } +end + +class DogValidatorReturningFalse < Dog + before_validation { false } + before_validation { self.history << 'before_validation_marker2' } +end + +class DogWithMissingName < Dog + before_validation { self.history << 'before_validation_marker' } + validates_presence_of :name +end + +class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase + + def test_before_validation_and_after_validation_callbacks_should_be_called + d = DogWithMethodCallbacks.new + d.valid? + assert_equal ['before_validation_marker', 'after_validation_marker'], d.history + end + + def test_before_validation_and_after_validation_callbacks_should_be_called_with_proc + d = DogValidtorsAreProc.new + d.valid? + assert_equal ['before_validation_marker', 'after_validation_marker'], d.history + end + + def test_before_validation_and_after_validation_callbacks_should_be_called_in_declared_order + d = DogWithTwoValidators.new + d.valid? + assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history + end + + def test_further_callbacks_should_not_be_called_if_before_validation_returns_false + d = DogValidatorReturningFalse.new + output = d.valid? + assert_equal [], d.history + assert_equal false, output + end + + def test_validation_test_should_be_done + d = DogWithMissingName.new + output = d.valid? + assert_equal ['before_validation_marker'], d.history + assert_equal false, output + end + +end diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 58a8d179ad..0679e67f84 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -8,131 +8,131 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase @person = Person.new end - # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) + # validates_inclusion_of: generate_message(attr_name, :inclusion, :message => custom_message, :value => value) def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') + assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :value => 'title') end def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :default => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :message => 'custom message %{value}', :value => 'title') end - # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) + # validates_exclusion_of: generate_message(attr_name, :exclusion, :message => custom_message, :value => value) def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') + assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :value => 'title') end def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :default => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :message => 'custom message %{value}', :value => 'title') end - # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) + # validates_format_of: generate_message(attr_name, :invalid, :message => custom_message, :value => value) def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') + assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :value => 'title') end def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :default => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :message => 'custom message %{value}', :value => 'title') end - # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) + # validates_confirmation_of: generate_message(attr_name, :confirmation, :message => custom_message) def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation, :default => nil) + assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation) end def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :default => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :message => 'custom message') end - # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) + # validates_acceptance_of: generate_message(attr_name, :accepted, :message => custom_message) def test_generate_message_accepted_with_default_message - assert_equal "must be accepted", @person.errors.generate_message(:title, :accepted, :default => nil) + assert_equal "must be accepted", @person.errors.generate_message(:title, :accepted) end def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :default => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :message => 'custom message') end - # add_on_empty: generate_message(attr, :empty, :default => custom_message) + # add_on_empty: generate_message(attr, :empty, :message => custom_message) def test_generate_message_empty_with_default_message - assert_equal "can't be empty", @person.errors.generate_message(:title, :empty, :default => nil) + assert_equal "can't be empty", @person.errors.generate_message(:title, :empty) end def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :default => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :message => 'custom message') end - # add_on_blank: generate_message(attr, :blank, :default => custom_message) + # add_on_blank: generate_message(attr, :blank, :message => custom_message) def test_generate_message_blank_with_default_message - assert_equal "can't be blank", @person.errors.generate_message(:title, :blank, :default => nil) + assert_equal "can't be blank", @person.errors.generate_message(:title, :blank) end def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :default => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :message => 'custom message') end - # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) + # validates_length_of: generate_message(attr, :too_long, :message => custom_message, :count => option_value.end) def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :default => nil, :count => 10) + assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :count => 10) end def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :default => 'custom message %{count}', :count => 10) + assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :message => 'custom message %{count}', :count => 10) end - # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) + # validates_length_of: generate_message(attr, :too_short, :default => custom_message, :count => option_value.begin) def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :default => nil, :count => 10) + assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :count => 10) end def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :default => 'custom message %{count}', :count => 10) + assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :message => 'custom message %{count}', :count => 10) end - # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) + # validates_length_of: generate_message(attr, :wrong_length, :message => custom_message, :count => option_value) def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) + assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :count => 10) end def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :default => 'custom message %{count}', :count => 10) + assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :message => 'custom message %{count}', :count => 10) end - # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) + # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :message => custom_message) def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') + assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :value => 'title') end def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :default => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :message => 'custom message %{value}', :value => 'title') end - # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) + # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => custom_message) def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) + assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :value => 'title', :count => 10) end def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) + assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :value => 'title', :count => 10) end def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) + assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :value => 'title', :count => 10) end def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) + assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :value => 'title', :count => 10) end def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) + assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :value => 'title', :count => 10) end def test_generate_message_odd_with_default_message - assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) + assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :value => 'title', :count => 10) end def test_generate_message_even_with_default_message - assert_equal "must be even", @person.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) + assert_equal "must be even", @person.errors.generate_message(:title, :even, :value => 'title', :count => 10) end end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 547d80f46e..e9f0e430fe 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -22,23 +22,23 @@ class I18nValidationTest < ActiveModel::TestCase end def test_errors_add_on_empty_generates_message - @person.errors.expects(:generate_message).with(:title, :empty, {:default => nil}) + @person.errors.expects(:generate_message).with(:title, :empty, {}) @person.errors.add_on_empty :title end def test_errors_add_on_empty_generates_message_with_custom_default_message - @person.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'}) - @person.errors.add_on_empty :title, 'custom' + @person.errors.expects(:generate_message).with(:title, :empty, {:message => 'custom'}) + @person.errors.add_on_empty :title, :message => 'custom' end def test_errors_add_on_blank_generates_message - @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) + @person.errors.expects(:generate_message).with(:title, :blank, {}) @person.errors.add_on_blank :title end def test_errors_add_on_blank_generates_message_with_custom_default_message - @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @person.errors.add_on_blank :title, 'custom' + @person.errors.expects(:generate_message).with(:title, :blank, {:message => 'custom'}) + @person.errors.add_on_blank :title, :message => 'custom' end def test_full_message_encoding @@ -62,441 +62,272 @@ class I18nValidationTest < ActiveModel::TestCase end # ActiveModel::Validations + + # A set of common cases for ActiveModel::Validations message generation that + # are used to generate tests to keep things DRY + # + COMMON_CASES = [ + # [ case, validation_options, generate_message_options] + [ "given no options", {}, {}], + [ "given custom message", {:message => "custom"}, {:message => "custom"}], + [ "given if condition", {:if => lambda { true }}, {}], + [ "given unless condition", {:unless => lambda { false }}, {}], + [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }] + ] + # validates_confirmation_of w/ mocha - def test_validates_confirmation_of_generates_message - Person.validates_confirmation_of :title - @person.title_confirmation = 'foo' - @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil}) - @person.valid? - end - def test_validates_confirmation_of_generates_message_with_custom_default_message - Person.validates_confirmation_of :title, :message => 'custom' - @person.title_confirmation = 'foo' - @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_confirmation_of on generated message #{name}" do + Person.validates_confirmation_of :title, validation_options + @person.title_confirmation = 'foo' + @person.errors.expects(:generate_message).with(:title, :confirmation, generate_message_options) + @person.valid? + end end # validates_acceptance_of w/ mocha - def test_validates_acceptance_of_generates_message - Person.validates_acceptance_of :title, :allow_nil => false - @person.errors.expects(:generate_message).with(:title, :accepted, {:default => nil}) - @person.valid? - end - - def test_validates_acceptance_of_generates_message_with_custom_default_message - Person.validates_acceptance_of :title, :message => 'custom', :allow_nil => false - @person.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_acceptance_of on generated message #{name}" do + Person.validates_acceptance_of :title, validation_options.merge(:allow_nil => false) + @person.errors.expects(:generate_message).with(:title, :accepted, generate_message_options) + @person.valid? + end end # validates_presence_of w/ mocha - def test_validates_presence_of_generates_message - Person.validates_presence_of :title - @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @person.valid? - end - - def test_validates_presence_of_generates_message_with_custom_default_message - Person.validates_presence_of :title, :message => 'custom' - @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_presence_of on generated message #{name}" do + Person.validates_presence_of :title, validation_options + @person.errors.expects(:generate_message).with(:title, :blank, generate_message_options) + @person.valid? + end end - # validates_length_of :within w/ mocha + # validates_length_of :within too short w/ mocha - def test_validates_length_of_within_generates_message_with_title_too_short - Person.validates_length_of :title, :within => 3..5 - @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_length_of for :withing on generated message when too short #{name}" do + Person.validates_length_of :title, validation_options.merge(:within => 3..5) + @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(:count => 3)) + @person.valid? + end end - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Person.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @person.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long - Person.validates_length_of :title, :within => 3..5 - @person.title = 'this title is too long' - @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @person.valid? - end + # validates_length_of :within too long w/ mocha - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Person.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @person.title = 'this title is too long' - @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_length_of for :too_long generated message #{name}" do + Person.validates_length_of :title, validation_options.merge(:within => 3..5) + @person.title = 'this title is too long' + @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(:count => 5)) + @person.valid? + end end # validates_length_of :is w/ mocha - def test_validates_length_of_is_generates_message - Person.validates_length_of :title, :is => 5 - @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil}) - @person.valid? - end - - def test_validates_length_of_is_generates_message_with_custom_default_message - Person.validates_length_of :title, :is => 5, :message => 'custom' - @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_length_of for :is on generated message #{name}" do + Person.validates_length_of :title, validation_options.merge(:is => 5) + @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(:count => 5)) + @person.valid? + end end # validates_format_of w/ mocha - def test_validates_format_of_generates_message - Person.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @person.title = '72x' - @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil}) - @person.valid? - end - - def test_validates_format_of_generates_message_with_custom_default_message - Person.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' - @person.title = '72x' - @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_format_of on generated message #{name}" do + Person.validates_format_of :title, validation_options.merge(:with => /^[1-9][0-9]*$/) + @person.title = '72x' + @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(:value => '72x')) + @person.valid? + end end # validates_inclusion_of w/ mocha - def test_validates_inclusion_of_generates_message - Person.validates_inclusion_of :title, :in => %w(a b c) - @person.title = 'z' - @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil}) - @person.valid? - end - - def test_validates_inclusion_of_generates_message_with_custom_default_message - Person.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom' - @person.title = 'z' - @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_inclusion_of on generated message #{name}" do + Person.validates_inclusion_of :title, validation_options.merge(:in => %w(a b c)) + @person.title = 'z' + @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(:value => 'z')) + @person.valid? + end end # validates_exclusion_of w/ mocha - def test_validates_exclusion_of_generates_message - Person.validates_exclusion_of :title, :in => %w(a b c) - @person.title = 'a' - @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil}) - @person.valid? - end - - def test_validates_exclusion_of_generates_message_with_custom_default_message - Person.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom' - @person.title = 'a' - @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_exclusion_of generated message #{name}" do + Person.validates_exclusion_of :title, validation_options.merge(:in => %w(a b c)) + @person.title = 'a' + @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(:value => 'a')) + @person.valid? + end end # validates_numericality_of without :only_integer w/ mocha - def test_validates_numericality_of_generates_message - Person.validates_numericality_of :title - @person.title = 'a' - @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @person.valid? - end - - def test_validates_numericality_of_generates_message_with_custom_default_message - Person.validates_numericality_of :title, :message => 'custom' - @person.title = 'a' - @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_numericality_of generated message #{name}" do + Person.validates_numericality_of :title, validation_options + @person.title = 'a' + @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(:value => 'a')) + @person.valid? + end end # validates_numericality_of with :only_integer w/ mocha - def test_validates_numericality_of_only_integer_generates_message - Person.validates_numericality_of :title, :only_integer => true - @person.title = '0.0' - @person.errors.expects(:generate_message).with(:title, :not_an_integer, {:value => '0.0', :default => nil}) - @person.valid? - end - - def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message - Person.validates_numericality_of :title, :only_integer => true, :message => 'custom' - @person.title = '0.0' - @person.errors.expects(:generate_message).with(:title, :not_an_integer, {:value => '0.0', :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_numericality_of for :only_integer on generated message #{name}" do + Person.validates_numericality_of :title, validation_options.merge(:only_integer => true) + @person.title = '0.0' + @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(:value => '0.0')) + @person.valid? + end end # validates_numericality_of :odd w/ mocha - def test_validates_numericality_of_odd_generates_message - Person.validates_numericality_of :title, :only_integer => true, :odd => true - @person.title = 0 - @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil}) - @person.valid? - end - - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Person.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' - @person.title = 0 - @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_numericality_of for :odd on generated message #{name}" do + Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :odd => true) + @person.title = 0 + @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(:value => 0)) + @person.valid? + end end # validates_numericality_of :less_than w/ mocha - def test_validates_numericality_of_less_than_generates_message - Person.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @person.title = 1 - @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil}) - @person.valid? + COMMON_CASES.each do |name, validation_options, generate_message_options| + test "validates_numericality_of for :less_than on generated message #{name}" do + Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :less_than => 0) + @person.title = 1 + @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(:value => 1, :count => 0)) + @person.valid? + end end - def test_validates_numericality_of_less_than_odd_generates_message_with_custom_default_message - Person.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' - @person.title = 1 - @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'}) - @person.valid? - end - # validates_confirmation_of w/o mocha + # To make things DRY this macro is defined to define 3 tests for every validation case. + def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation) + # test "validates_confirmation_of finds custom model key translation when blank" + test "#{validation} finds custom model key translation when #{error_type}" do + I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} - def test_validates_confirmation_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}} + yield(@person, {}) + @person.valid? + assert_equal ['custom message'], @person.errors[:title] + end - Person.validates_confirmation_of :title - @person.title_confirmation = 'foo' - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end + # test "validates_confirmation_of finds custom model key translation with interpolation when blank" + test "#{validation} finds custom model key translation with interpolation when #{error_type}" do + I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message with %{extra}'}}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} - def test_validates_confirmation_of_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}} + yield(@person, {:extra => "extra information"}) + @person.valid? + assert_equal ['custom message with extra information'], @person.errors[:title] + end - Person.validates_confirmation_of :title - @person.title_confirmation = 'foo' - @person.valid? - assert_equal ['global message'], @person.errors[:title] - end + # test "validates_confirmation_of finds global default key translation when blank" + test "#{validation} finds global default key translation when #{error_type}" do + I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} - # validates_acceptance_of w/o mocha - - def test_validates_acceptance_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}} - - Person.validates_acceptance_of :title, :allow_nil => false - @person.valid? - assert_equal ['custom message'], @person.errors[:title] + yield(@person, {}) + @person.valid? + assert_equal ['global message'], @person.errors[:title] + end end - def test_validates_acceptance_of_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}} + # validates_confirmation_of w/o mocha - Person.validates_acceptance_of :title, :allow_nil => false - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_confirmation_of", :confirmation do |person, options_to_merge| + Person.validates_confirmation_of :title, options_to_merge + person.title_confirmation = 'foo' end - # validates_presence_of w/o mocha - - def test_validates_presence_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}} + # validates_acceptance_of w/o mocha - Person.validates_presence_of :title - @person.valid? - assert_equal ['custom message'], @person.errors[:title] + set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge| + Person.validates_acceptance_of :title, options_to_merge.merge(:allow_nil => false) end - def test_validates_presence_of_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}} + # validates_presence_of w/o mocha - Person.validates_presence_of :title - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_presence_of", :blank do |person, options_to_merge| + Person.validates_presence_of :title, options_to_merge end # validates_length_of :within w/o mocha - def test_validates_length_of_within_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}} - - Person.validates_length_of :title, :within => 3..5 - @person.valid? - assert_equal ['custom message'], @person.errors[:title] + set_expectations_for_validation "validates_length_of", :too_short do |person, options_to_merge| + Person.validates_length_of :title, options_to_merge.merge(:within => 3..5) end - def test_validates_length_of_within_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}} - - Person.validates_length_of :title, :within => 3..5 - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_length_of", :too_long do |person, options_to_merge| + Person.validates_length_of :title, options_to_merge.merge(:within => 3..5) + person.title = "too long" end # validates_length_of :is w/o mocha - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}} - - Person.validates_length_of :title, :is => 5 - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}} - - Person.validates_length_of :title, :is => 5 - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_length_of", :wrong_length do |person, options_to_merge| + Person.validates_length_of :title, options_to_merge.merge(:is => 5) end # validates_format_of w/o mocha - def test_validates_format_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} - - Person.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_format_of_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} - - Person.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge| + Person.validates_format_of :title, options_to_merge.merge(:with => /^[1-9][0-9]*$/) end # validates_inclusion_of w/o mocha - def test_validates_inclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}} - - Person.validates_inclusion_of :title, :in => %w(a b c) - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_inclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}} - - Person.validates_inclusion_of :title, :in => %w(a b c) - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_inclusion_of", :inclusion do |person, options_to_merge| + Person.validates_inclusion_of :title, options_to_merge.merge(:in => %w(a b c)) end # validates_exclusion_of w/o mocha - def test_validates_exclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}} - - Person.validates_exclusion_of :title, :in => %w(a b c) - @person.title = 'a' - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_exclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}} - - Person.validates_exclusion_of :title, :in => %w(a b c) - @person.title = 'a' - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge| + Person.validates_exclusion_of :title, options_to_merge.merge(:in => %w(a b c)) + person.title = 'a' end # validates_numericality_of without :only_integer w/o mocha - def test_validates_numericality_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}} - - Person.validates_numericality_of :title - @person.title = 'a' - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_numericality_of_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}} - - Person.validates_numericality_of :title, :only_integer => true - @person.title = 'a' - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_numericality_of", :not_a_number do |person, options_to_merge| + Person.validates_numericality_of :title, options_to_merge + person.title = 'a' end # validates_numericality_of with :only_integer w/o mocha - def test_validates_numericality_of_only_integer_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_an_integer => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:not_an_integer => 'global message'}} - - Person.validates_numericality_of :title, :only_integer => true - @person.title = '1.0' - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_numericality_of_only_integer_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:not_an_integer => 'global message'}} - - Person.validates_numericality_of :title, :only_integer => true - @person.title = '1.0' - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge| + Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true) + person.title = '1.0' end # validates_numericality_of :odd w/o mocha - def test_validates_numericality_of_odd_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}} - - Person.validates_numericality_of :title, :only_integer => true, :odd => true - @person.title = 0 - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_numericality_of_odd_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}} - - Person.validates_numericality_of :title, :only_integer => true, :odd => true - @person.title = 0 - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge| + Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :odd => true) + person.title = 0 end # validates_numericality_of :less_than w/o mocha - def test_validates_numericality_of_less_than_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}} - - Person.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @person.title = 1 - @person.valid? - assert_equal ['custom message'], @person.errors[:title] - end - - def test_validates_numericality_of_less_than_finds_global_default_translation - I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}} - - Person.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @person.title = 1 - @person.valid? - assert_equal ['global message'], @person.errors[:title] + set_expectations_for_validation "validates_numericality_of", :less_than do |person, options_to_merge| + Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :less_than => 0) + person.title = 1 end # test with validates_with diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 8e77a0222e..e1d7d40c47 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -4,6 +4,8 @@ require 'cases/helper' require 'models/topic' require 'models/person' +require 'bigdecimal' + class NumericalityValidationTest < ActiveModel::TestCase def teardown diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb index b1450586a8..510c13a7c3 100644 --- a/activemodel/test/cases/validations/presence_validation_test.rb +++ b/activemodel/test/cases/validations/presence_validation_test.rb @@ -32,7 +32,7 @@ class PresenceValidationTest < ActiveModel::TestCase assert t.valid? end - test 'accepts array arguments' do + def test_accepts_array_arguments Topic.validates_presence_of %w(title content) t = Topic.new assert t.invalid? diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index af195af080..e94d8ce88c 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -6,6 +6,9 @@ require 'models/reply' require 'models/custom_reader' require 'models/automobile' +require 'active_support/json' +require 'active_support/xml_mini' + class ValidationsTest < ActiveModel::TestCase def setup @@ -83,10 +86,9 @@ class ValidationsTest < ActiveModel::TestCase r = Reply.new r.content = "Mismatch" r.valid? - r.errors[:base] << "Reply is not dignifying" + r.errors.add(:base, "Reply is not dignifying") - errors = [] - r.errors.to_a.each { |error| errors << error } + errors = r.errors.to_a.inject([]) { |result, error| result + [error] } assert_equal ["Reply is not dignifying"], r.errors[:base] @@ -95,6 +97,22 @@ class ValidationsTest < ActiveModel::TestCase assert_equal 2, r.errors.count end + def test_errors_on_base_with_symbol_message + r = Reply.new + r.content = "Mismatch" + r.valid? + r.errors.add(:base, :invalid) + + errors = r.errors.to_a.inject([]) { |result, error| result + [error] } + + assert_equal ["is invalid"], r.errors[:base] + + assert errors.include?("Title is Empty") + assert errors.include?("is invalid") + + assert_equal 2, r.errors.count + end + def test_errors_empty_after_errors_on_check t = Topic.new assert t.errors[:id].empty? @@ -143,12 +161,18 @@ class ValidationsTest < ActiveModel::TestCase end end - def test_errors_to_xml - r = Reply.new :title => "Wrong Create" - assert r.invalid? - xml = r.errors.to_xml(:skip_instruct => true) - assert_equal "<errors>", xml.first(8) - assert xml.include?("<error>Content is Empty</error>") + def test_errors_conversions + Topic.validates_presence_of %w(title content) + t = Topic.new + assert t.invalid? + + xml = t.errors.to_xml + assert_match %r{<errors>}, xml + assert_match %r{<error>Title can't be blank</error>}, xml + assert_match %r{<error>Content can't be blank</error>}, xml + + json = t.errors.to_json + assert_equal t.errors.to_a.to_json, json end def test_validation_order |