From acc7c471d4c49a541fc2f38c27cd13334d1d8260 Mon Sep 17 00:00:00 2001 From: Sven Fuchs + Mateo Murphy Date: Sat, 22 Aug 2009 19:52:02 +0200 Subject: [PATCH] Fix ActiveRecord Error message I18n: * allow messages and full_messages to be lazily translated at any time * allow locales to be swapped to obtain correctly localized messages * allow localized global and error-type specific full_message formats * extract an Error class --- activerecord/lib/active_record/locale/en.yml | 3 + activerecord/lib/active_record/validations.rb | 175 ++-- activerecord/test/cases/validations_i18n_test.rb | 1292 +++++++++++----------- 3 files changed, 758 insertions(+), 712 deletions(-) diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index bf8a71d..98a2e23 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -25,6 +25,9 @@ en: even: "must be even" # Append your own errors here or at the model/attributes scope. + full_messages: + format: "{{attribute}} {{message}}" + # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. # diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 7a7d0ab..b788ef6 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -14,11 +14,86 @@ module ActiveRecord end end + class Error + attr_accessor :base, :attribute, :type, :options + + def initialize(base, attribute, type = nil, options = {}) + @base = base + @attribute = attribute + @type = type + @options = options + @message = options.delete(:message) || type + end + + def message + generate_message(@message, options.dup) + end + + def full_message + attribute.to_s == 'base' ? message : generate_full_message(message, options.dup) + end + + alias :to_s :message + + def value + @base.respond_to?(attribute) ? @base.send(attribute) : nil + end + + protected + + # Translates an error message in it's default scope (activerecord.errrors.messages). + # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there, + # it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the + # default message (e.g. activerecord.errors.messages.MESSAGE). 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 class Admin < User; end and you wanted the translation for the :blank + # error +message+ for the title +attribute+, it looks for these translations: + # + #
    + #
  1. activerecord.errors.models.admin.attributes.title.blank
  2. + #
  3. activerecord.errors.models.admin.blank
  4. + #
  5. activerecord.errors.models.user.attributes.title.blank
  6. + #
  7. activerecord.errors.models.user.blank
  8. + #
  9. activerecord.errors.messages.blank
  10. + #
  11. any default you provided through the +options+ hash (in the activerecord.errors scope)
  12. + #
+ def generate_message(message, options = {}) + options.reverse_merge! :model => @base.class.human_name, + :attribute => @base.class.human_attribute_name(attribute.to_s), + :value => value + + keys = @base.class.self_and_descendants_from_active_record.map do |klass| + [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", + :"models.#{klass.name.underscore}.#{message}" ] + end.flatten + + keys << options.delete(:default) if options[:default] + keys << :"messages.#{message}" + keys << message if message.is_a?(String) + + I18n.translate(keys.shift, options.merge(:default => keys, :scope => [:activerecord, :errors])) + end + + def generate_full_message(message, options = {}) + options.reverse_merge! :message => self.message, + :model => @base.class.human_name, + :attribute => @base.class.human_attribute_name(attribute.to_s), + :value => value + + key = :"full_messages.#{@message}" + defaults = [:'full_messages.format', '{{attribute}} {{message}}'] + + I18n.t(key, options.merge(:default => defaults, :scope => [:activerecord, :errors])) + end + end + # Active Record validation is reported to and from this object, which is used by Base#save to # determine whether the object is in a valid state to be saved. See usage example in Validations. class Errors include Enumerable - + class << self def default_error_messages ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages').") @@ -43,11 +118,17 @@ module ActiveRecord # error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute). # 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) + # @errors[attribute.to_s] ||= [] + # @errors[attribute.to_s] << message + # end + def add(attribute, message = nil, options = {}) - message ||= :invalid - message = generate_message(attribute, message, options) if message.is_a?(Symbol) + options[:message] = options.delete(:default) if options.has_key?(:default) @errors[attribute.to_s] ||= [] - @errors[attribute.to_s] << message + @errors[attribute.to_s] << Error.new(@base, attribute, message, options) end # Will add an error message to each of the attributes in +attributes+ that is empty. @@ -66,49 +147,6 @@ module ActiveRecord add(attr, :blank, :default => custom_message) if value.blank? end end - - # Translates an error message in it's default scope (activerecord.errrors.messages). - # Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there, - # it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the - # default message (e.g. activerecord.errors.messages.MESSAGE). 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 class Admin < User; end and you wanted the translation for the :blank - # error +message+ for the title +attribute+, it looks for these translations: - # - #
    - #
  1. activerecord.errors.models.admin.attributes.title.blank
  2. - #
  3. activerecord.errors.models.admin.blank
  4. - #
  5. activerecord.errors.models.user.attributes.title.blank
  6. - #
  7. activerecord.errors.models.user.blank
  8. - #
  9. activerecord.errors.messages.blank
  10. - #
  11. any default you provided through the +options+ hash (in the activerecord.errors scope)
  12. - #
- 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 # Returns true if the specified +attribute+ has errors associated with it. # @@ -140,6 +178,7 @@ module ActiveRecord def on(attribute) errors = @errors[attribute.to_s] return nil if errors.nil? + errors.map!(&:to_s) errors.size == 1 ? errors.first : errors end @@ -163,7 +202,7 @@ module ActiveRecord # # name - can't be blank # # address - can't be blank def each - @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } } + @errors.each_key { |attr| @errors[attr].each { |error| yield attr, error.to_s } } end # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned @@ -194,22 +233,10 @@ module ActiveRecord # 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 = [] - - @errors.each_key do |attr| - @errors[attr].each do |message| - next unless message - - if attr == "base" - full_messages << message - else - attr_name = @base.class.human_attribute_name(attr) - full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message - end - end + @errors.values.inject([]) do |full_messages, errors| + full_messages + errors.map { |error| error.full_message } end - full_messages - end + end # Returns true if no errors have been added. def empty? @@ -254,7 +281,7 @@ module ActiveRecord full_messages.each { |msg| e.error(msg) } end end - + end @@ -437,7 +464,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation") - record.errors.add(attr_name, :confirmation, :default => configuration[:message]) + record.errors.add(attr_name, :confirmation, :default => configuration[:message]) end end end @@ -479,7 +506,7 @@ module ActiveRecord validates_each(attr_names,configuration) do |record, attr_name, value| unless value == configuration[:accept] - record.errors.add(attr_name, :accepted, :default => configuration[:message]) + record.errors.add(attr_name, :accepted, :default => configuration[:message]) end end end @@ -499,7 +526,7 @@ module ActiveRecord # # Configuration options: # * message - A custom error message (default is: "can't be blank"). - # * on - Specifies when this validation is active (default is :save, other options :create, + # * on - Specifies when this validation is active (default is :save, other options :create, # :update). # * if - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). @@ -599,7 +626,7 @@ module ActiveRecord 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) + record.errors.add(attr, key, :default => custom_message, :count => option_value) end end end @@ -687,7 +714,7 @@ module ActiveRecord # 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 @@ -696,7 +723,7 @@ module ActiveRecord # 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 @@ -794,7 +821,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| unless value.to_s =~ configuration[:with] - record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) + record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) end end end @@ -828,7 +855,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| unless enum.include?(value) - record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value) + record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value) end end end @@ -862,7 +889,7 @@ module ActiveRecord validates_each(attr_names, configuration) do |record, attr_name, value| if enum.include?(value) - record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value) + record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value) end end end @@ -970,7 +997,7 @@ module ActiveRecord case option when :odd, :even unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[] - record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message]) + record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message]) end else record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]] diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb index 1585554..7db94d3 100644 --- a/activerecord/test/cases/validations_i18n_test.rb +++ b/activerecord/test/cases/validations_i18n_test.rb @@ -1,32 +1,20 @@ require "cases/helper" require 'models/topic' require 'models/reply' +require 'models/parrot' -class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase - def setup - reset_callbacks Topic - @topic = Topic.new - @old_load_path, @old_backend = I18n.load_path, I18n.backend - I18n.load_path.clear - I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}}) +module ActiveRecordValidationsI18nTestHelper + def store_translations(*args) + data = args.extract_options! + locale = args.shift || 'en' + I18n.backend.send(:init_translations) + I18n.backend.store_translations(locale, :activerecord => data) end - def teardown - reset_callbacks Topic - I18n.load_path.replace @old_load_path - I18n.backend = @old_backend - end - - def unique_topic - @unique ||= Topic.create :title => 'unique!' - end - - def replied_topic - @replied_topic ||= begin - topic = Topic.create(:title => "topic") - topic.replies << Reply.new - topic + def delete_translation(key) + I18n.backend.instance_eval do + keys = I18n.send(:normalize_translation_keys, 'en', key, nil) + keys.inject(translations) { |result, k| keys.last == k ? result.delete(k.to_sym) : result[k.to_sym] } end end @@ -37,885 +25,913 @@ class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new) end end +end + +# DEPRECATIONS - def test_default_error_messages_is_deprecated +class ActiveRecordValidationsI18nDeprecationsTests < ActiveSupport::TestCase + test "default_error_messages is deprecated and can be removed in Rails 3 / ActiveModel" do assert_deprecated('ActiveRecord::Errors.default_error_messages') do ActiveRecord::Errors.default_error_messages end end - def test_percent_s_interpolation_syntax_in_error_messages_still_works + test "%s interpolation syntax in error messages still works" do ActiveSupport::Deprecation.silence do result = I18n.t :does_not_exist, :default => "%s interpolation syntax is deprecated", :value => 'this' assert_equal result, "this interpolation syntax is deprecated" end end - def test_percent_s_interpolation_syntax_in_error_messages_is_deprecated + test "%s interpolation syntax in error messages is deprecated" do assert_deprecated('using %s in messages') do - I18n.t :does_not_exist, :default => "%s interpolation syntax is deprecated", :value => 'this' + I18n.t :does_not_exist, :default => "%s interpolation syntax is deprected", :value => 'this' end end - def test_percent_d_interpolation_syntax_in_error_messages_still_works + test "%d interpolation syntax in error messages still works" do ActiveSupport::Deprecation.silence do result = I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprecated", :count => 2 assert_equal result, "2 interpolation syntaxes are deprecated" end end - def test_percent_d_interpolation_syntax_in_error_messages_is_deprecated + test "%d interpolation syntax in error messages is deprecated" do assert_deprecated('using %d in messages') do I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprected", :count => 2 end end +end - def test_percent_s_interpolation_syntax_not_changed_when_no_values_were_passed - assert_not_deprecated do - I18n.t :does_not_exist, :default => "%d interpolation syntaxes are deprected" - end - end - - # ActiveRecord::Errors - def test_errors_generate_message_translates_custom_model_attribute_key - - I18n.expects(:translate).with( - :topic, - { :count => 1, - :default => ['Topic'], - :scope => [:activerecord, :models] - } - ).returns('Topic') - - I18n.expects(:translate).with( - :"topic.title", - { :count => 1, - :default => ['Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') - - I18n.expects(:translate).with( - :"models.topic.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.topic.invalid", - 'default from class def error 1', - :"messages.invalid"], - :attribute => "Title", - :model => "Topic" - ).returns('default from class def error 1') - - @topic.errors.generate_message :title, :invalid, :default => 'default from class def error 1' - end - - def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti - - I18n.expects(:translate).with( - :reply, - { :count => 1, - :default => [:topic, 'Reply'], - :scope => [:activerecord, :models] - } - ).returns('Reply') - - I18n.expects(:translate).with( - :"reply.title", - { :count => 1, - :default => [:'topic.title', 'Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') - I18n.expects(:translate).with( - :"models.reply.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.reply.invalid", - :"models.topic.attributes.title.invalid", - :"models.topic.invalid", - 'default from class def', - :"messages.invalid"], - :model => 'Reply', - :attribute => 'Title' - ).returns("default from class def") +# ACTIVERECORD VALIDATIONS +# +# For each validation: +# +# * test expect that it adds an error with the appropriate arguments +# * test that it looks up the correct default message - Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def' +class ActiveRecordValidationsI18nTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper + def setup + reset_callbacks(Topic) + @topic = Topic.new + @reply = Reply.new + @old_load_path, @old_backend = I18n.load_path, I18n.backend + I18n.load_path.clear + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}}) end - def test_errors_add_on_empty_generates_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil}) - @topic.errors.add_on_empty :title + def teardown + reset_callbacks(Topic) + I18n.load_path.replace(@old_load_path) + I18n.backend = @old_backend end - def test_errors_add_on_empty_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'}) - @topic.errors.add_on_empty :title, 'custom' + def expect_error_added(model, attribute, type, options) + model.errors.expects(:add).with(attribute, type, options) + yield + model.valid? end - def test_errors_add_on_blank_generates_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.errors.add_on_blank :title + def assert_message_translations(model, attribute, type, &block) + assert_default_message_translation(model, attribute, type, &block) + reset_callbacks(model.class) + model.errors.clear + assert_custom_message_translation(model, attribute, type, &block) end - def test_errors_add_on_blank_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.errors.add_on_blank :title, 'custom' + def assert_custom_message_translation(model, attribute, type) + store_translations(:errors => { :models => { model.class.name.underscore => { :attributes => { attribute => { type => 'custom message' } } } } }) + yield + model.valid? + assert_equal 'custom message', model.errors.on(attribute) end - def test_errors_full_messages_translates_human_attribute_name_for_model_attributes - @topic.errors.instance_variable_set :@errors, { 'title' => ['empty'] } - I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title') - @topic.errors.full_messages :locale => 'en' + def assert_default_message_translation(model, attribute, type) + store_translations(:errors => { :messages => { type => 'default message' } }) + yield + model.valid? + assert_equal 'default message', model.errors.on(attribute) end - # ActiveRecord::Validations - # validates_confirmation_of w/ mocha - def test_validates_confirmation_of_generates_message - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil}) - @topic.valid? + def unique_topic + @unique ||= Topic.create(:title => 'unique!') end - def test_validates_confirmation_of_generates_message_with_custom_default_message - Topic.validates_confirmation_of :title, :message => 'custom' - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'}) - @topic.valid? + def replied_topic + @replied_topic ||= begin + topic = Topic.create(:title => "topic") + topic.replies << Reply.new + topic + end end - # validates_acceptance_of w/ mocha - - def test_validates_acceptance_of_generates_message - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil}) - @topic.valid? - end + # validates_confirmation_of - def test_validates_acceptance_of_generates_message_with_custom_default_message - Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'}) - @topic.valid? + test "#validates_confirmation_of given no custom message" do + expect_error_added(@topic, :title, :confirmation, :default => nil) do + Topic.validates_confirmation_of :title + @topic.title = 'title' + @topic.title_confirmation = 'foo' + end end - # validates_presence_of w/ mocha - - def test_validates_presence_of_generates_message - Topic.validates_presence_of :title - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.valid? + test "#validates_confirmation_of given a custom message" do + expect_error_added(@topic, :title, :confirmation, :default => 'custom') do + Topic.validates_confirmation_of :title, :message => 'custom' + @topic.title_confirmation = 'foo' + end end - def test_validates_presence_of_generates_message_with_custom_default_message - Topic.validates_presence_of :title, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.valid? + test "#validates_confirmation_of finds the correct message translations" do + assert_message_translations(@topic, :title, :confirmation) do + Topic.validates_confirmation_of :title + @topic.title_confirmation = 'foo' + end end - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? - end + # validates_acceptance_of - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? + test "#validates_acceptance_of given no custom message" do + expect_error_added(@topic, :title, :accepted, :default => nil) do + Topic.validates_acceptance_of :title, :allow_nil => false + end end - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? + test "#validates_acceptance_of given a custom message" do + expect_error_added(@topic, :title, :accepted, :default => 'custom') do + Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false + end end - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? + test "#validates_acceptance_of finds the correct message translations" do + assert_message_translations(@topic, :title, :accepted) do + Topic.validates_acceptance_of :title, :allow_nil => false + end end - # validates_length_of :within w/ mocha - - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? - end + # validates_presence_of - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? + test "#validates_presence_of given no custom message" do + expect_error_added(@topic, :title, :blank, :default => nil) do + Topic.validates_presence_of :title + end end - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? + test "#validates_presence_of given a custom message" do + expect_error_added(@topic, :title, :blank, :default => 'custom') do + Topic.validates_presence_of :title, :message => 'custom' + end end - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? + test "#validates_presence_of finds the correct message translations" do + assert_message_translations(@topic, :title, :blank) do + Topic.validates_presence_of :title + end end - # validates_length_of :is w/ mocha + # validates_length_of :too_short - def test_validates_length_of_is_generates_message - Topic.validates_length_of :title, :is => 5 - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil}) - @topic.valid? + test "#validates_length_of (:too_short) and no custom message" do + expect_error_added(@topic, :title, :too_short, :default => nil, :count => 3) do + Topic.validates_length_of :title, :within => 3..5 + end end - def test_validates_length_of_is_generates_message_with_custom_default_message - Topic.validates_length_of :title, :is => 5, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'}) - @topic.valid? + test "#validates_length_of (:too_short) and a custom message" do + expect_error_added(@topic, :title, :too_short, :default => 'custom', :count => 3) do + Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' + end end - # validates_uniqueness_of w/ mocha - - def test_validates_uniqueness_of_generates_message - Topic.validates_uniqueness_of :title - @topic.title = unique_topic.title - @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil, :value => 'unique!'}) - @topic.valid? + test "#validates_length_of (:too_short) finds the correct message translations" do + assert_message_translations(@topic, :title, :too_short) do + Topic.validates_length_of :title, :within => 3..5 + end end - def test_validates_uniqueness_of_generates_message_with_custom_default_message - Topic.validates_uniqueness_of :title, :message => 'custom' - @topic.title = unique_topic.title - @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom', :value => 'unique!'}) - @topic.valid? - end + # validates_length_of :too_long - # validates_format_of w/ mocha + test "#validates_length_of (:too_long) and no custom message" do + expect_error_added(@topic, :title, :too_long, :default => nil, :count => 5) do + Topic.validates_length_of :title, :within => 3..5 + @topic.title = 'this title is too long' + end + end - def test_validates_format_of_generates_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil}) - @topic.valid? + test "#validates_length_of (:too_long) and a custom message" do + expect_error_added(@topic, :title, :too_long, :default => 'custom', :count => 5) do + Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' + @topic.title = 'this title is too long' + end end - def test_validates_format_of_generates_message_with_custom_default_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'}) - @topic.valid? + test "#validates_length_of (:too_long) finds the correct message translations" do + assert_message_translations(@topic, :title, :too_long) do + Topic.validates_length_of :title, :within => 3..5 + @topic.title = 'this title is too long' + end end - # validates_inclusion_of w/ mocha + # validates_length_of :is - def test_validates_inclusion_of_generates_message - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil}) - @topic.valid? + test "#validates_length_of (:is) and no custom message" do + expect_error_added(@topic, :title, :wrong_length, :default => nil, :count => 5) do + Topic.validates_length_of :title, :is => 5 + @topic.title = 'this title has the wrong length' + end end - def test_validates_inclusion_of_generates_message_with_custom_default_message - Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'}) - @topic.valid? + test "#validates_length_of (:is) and a custom message" do + expect_error_added(@topic, :title, :wrong_length, :default => 'custom', :count => 5) do + Topic.validates_length_of :title, :is => 5, :wrong_length => 'custom' + @topic.title = 'this title has the wrong length' + end end - # validates_exclusion_of w/ mocha - - def test_validates_exclusion_of_generates_message - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil}) - @topic.valid? + test "#validates_length_of (:is) finds the correct message translations" do + assert_message_translations(@topic, :title, :wrong_length) do + Topic.validates_length_of :title, :is => 5 + @topic.title = 'this title has the wrong length' + end end - def test_validates_exclusion_of_generates_message_with_custom_default_message - Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'}) - @topic.valid? - end + # validates_uniqueness_of - # validates_numericality_of without :only_integer w/ mocha + test "#validates_uniqueness_of and no custom message" do + expect_error_added(@topic, :title, :taken, :default => nil, :value => 'unique!') do + Topic.validates_uniqueness_of :title + @topic.title = unique_topic.title + end + end - def test_validates_numericality_of_generates_message - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? + test "#validates_uniqueness_of and a custom message" do + expect_error_added(@topic, :title, :taken, :default => 'custom', :value => 'unique!') do + Topic.validates_uniqueness_of :title, :message => 'custom' + @topic.title = unique_topic.title + end end - def test_validates_numericality_of_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? + test "#validates_uniqueness_of finds the correct message translations" do + assert_message_translations(@topic, :title, :taken) do + Topic.validates_uniqueness_of :title + @topic.title = unique_topic.title + end end - # validates_numericality_of with :only_integer w/ mocha + # validates_format_of - def test_validates_numericality_of_only_integer_generates_message - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? + test "#validates_format_of and no custom message" do + expect_error_added(@topic, :title, :invalid, :default => nil, :value => '72x') do + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ + @topic.title = '72x' + end end - def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? + test "#validates_format_of and a custom message" do + expect_error_added(@topic, :title, :invalid, :default => 'custom', :value => '72x') do + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' + @topic.title = '72x' + end end - # validates_numericality_of :odd w/ mocha - - def test_validates_numericality_of_odd_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil}) - @topic.valid? + test "#validates_format_of finds the correct message translations" do + assert_message_translations(@topic, :title, :invalid) do + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ + @topic.title = '72x' + end end - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'}) - @topic.valid? - end + # validates_inclusion_of - # validates_numericality_of :less_than w/ mocha + test "#validates_inclusion_of and no custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :inclusion, :default => nil, :value => 'z') do + Topic.validates_inclusion_of :title, :in => list + @topic.title = 'z' + end + end - def test_validates_numericality_of_less_than_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil}) - @topic.valid? + test "#validates_inclusion_of and a custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :inclusion, :default => 'custom', :value => 'z') do + Topic.validates_inclusion_of :title, :in => list, :message => 'custom' + @topic.title = 'z' + end end - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'}) - @topic.valid? + test "#validates_inclusion_of finds the correct message translations" do + list = %w(a b c) + assert_message_translations(@topic, :title, :inclusion) do + Topic.validates_inclusion_of :title, :in => list + @topic.title = 'z' + end end - # validates_associated w/ mocha + # validates_exclusion_of - def test_validates_associated_generates_message - Topic.validates_associated :replies - replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil}) - replied_topic.valid? + test "#validates_exclusion_of and no custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :exclusion, :default => nil, :value => 'a') do + Topic.validates_exclusion_of :title, :in => list + @topic.title = 'a' + end end - def test_validates_associated_generates_message_with_custom_default_message - Topic.validates_associated :replies - replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil}) - replied_topic.valid? + test "#validates_exclusion_of and a custom message" do + list = %w(a b c) + expect_error_added(@topic, :title, :exclusion, :default => 'custom', :value => 'a') do + Topic.validates_exclusion_of :title, :in => list, :message => 'custom' + @topic.title = 'a' + end end - # validates_confirmation_of w/o mocha + test "#validates_exclusion_of finds the correct message translations" do + list = %w(a b c) + assert_message_translations(@topic, :title, :exclusion) do + Topic.validates_exclusion_of :title, :in => list + @topic.title = 'a' + end + end - def test_validates_confirmation_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} + # validates_numericality_of :not_a_number, without :only_integer - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:not_a_number, w/o :only_integer) no custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => nil, :value => 'a') do + Topic.validates_numericality_of :title + @topic.title = 'a' + end end - def test_validates_confirmation_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} - - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:not_a_number, w/o :only_integer) and a custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => 'custom', :value => 'a') do + Topic.validates_numericality_of :title, :message => 'custom' + @topic.title = 'a' + end end - # validates_acceptance_of w/o mocha + test "#validates_numericality_of (:not_a_number, w/o :only_integer) finds the correct message translations" do + assert_message_translations(@topic, :title, :not_a_number) do + Topic.validates_numericality_of :title + @topic.title = 'a' + end + end - def test_validates_acceptance_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} + # validates_numericality_of :not_a_number, with :only_integer - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:not_a_number, with :only_integer) no custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => nil, :value => 'a') do + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + end end - def test_validates_acceptance_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} - - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:not_a_number, with :only_integer) and a custom message" do + expect_error_added(@topic, :title, :not_a_number, :default => 'custom', :value => 'a') do + Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom' + @topic.title = 'a' + end end - # validates_presence_of w/o mocha + test "#validates_numericality_of (:not_a_number, with :only_integer) finds the correct message translations" do + assert_message_translations(@topic, :title, :not_a_number) do + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + end + end - def test_validates_presence_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} + # validates_numericality_of :odd - Topic.validates_presence_of :title - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:odd) no custom message" do + expect_error_added(@topic, :title, :odd, :default => nil, :value => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :odd => true + @topic.title = 0 + end end - def test_validates_presence_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} - - Topic.validates_presence_of :title - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:odd) and a custom message" do + expect_error_added(@topic, :title, :odd, :default => 'custom', :value => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' + @topic.title = 0 + end end - # validates_length_of :within w/o mocha + test "#validates_numericality_of (:odd) finds the correct message translations" do + assert_message_translations(@topic, :title, :odd) do + Topic.validates_numericality_of :title, :only_integer => true, :odd => true + @topic.title = 0 + end + end - def test_validates_length_of_within_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} + # validates_numericality_of :even - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:even) no custom message" do + expect_error_added(@topic, :title, :even, :default => nil, :value => 1) do + Topic.validates_numericality_of :title, :only_integer => true, :even => true + @topic.title = 1 + end end - def test_validates_length_of_within_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} - - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:even) and a custom message" do + expect_error_added(@topic, :title, :even, :default => 'custom', :value => 1) do + Topic.validates_numericality_of :title, :only_integer => true, :even => true, :message => 'custom' + @topic.title = 1 + end end - # validates_length_of :is w/o mocha + test "#validates_numericality_of (:even) finds the correct message translations" do + assert_message_translations(@topic, :title, :even) do + Topic.validates_numericality_of :title, :only_integer => true, :even => true + @topic.title = 1 + end + end - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + # validates_numericality_of :less_than - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_numericality_of (:less_than) no custom message" do + expect_error_added(@topic, :title, :less_than, :default => nil, :value => 1, :count => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 + @topic.title = 1 + end end - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_numericality_of (:less_than) and a custom message" do + expect_error_added(@topic, :title, :less_than, :default => 'custom', :value => 1, :count => 0) do + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' + @topic.title = 1 + end end - # validates_uniqueness_of w/o mocha + test "#validates_numericality_of (:less_than) finds the correct message translations" do + assert_message_translations(@topic, :title, :less_than) do + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 + @topic.title = 1 + end + end - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + # validates_associated - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#validates_associated no custom message" do + expect_error_added(replied_topic, :replies, :invalid, :default => nil, :value => replied_topic.replies) do + Topic.validates_associated :replies + end end - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + test "#validates_associated and a custom message" do + expect_error_added(replied_topic, :replies, :invalid, :default => 'custom', :value => replied_topic.replies) do + Topic.validates_associated :replies, :message => 'custom' + end + end - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#validates_associated finds the correct message translations" do + assert_message_translations(replied_topic, :replies, :invalid) do + Topic.validates_associated :replies + end end +end - # validates_format_of w/o mocha +# ACTIVERECORD ERROR +# +# * test that it passes given interpolation arguments, the human model name and human attribute name +# * test that it looks messages up with the the correct keys +# * test that it looks up the correct default messages - def test_validates_format_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} +class ActiveRecordErrorI18nTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + def setup + @reply = Reply.new + @old_backend, I18n.backend = I18n.backend, I18n::Backend::Simple.new end - def test_validates_format_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + def teardown + I18n.backend = @old_backend + I18n.locale = nil end - # validates_inclusion_of w/o mocha - - def test_validates_inclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + def assert_error_message(message, *args) + assert_equal message, ActiveRecord::Error.new(@reply, *args).message end - def test_validates_inclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + def assert_full_message(message, *args) + assert_equal message, ActiveRecord::Error.new(@reply, *args).full_message end - # validates_exclusion_of w/o mocha - - def test_validates_exclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#generate_message passes the model attribute value for interpolation" do + store_translations(:errors => { :messages => { :foo => "You fooed: {{value}}." } }) + @reply.title = "da title" + assert_error_message 'You fooed: da title.', :title, :foo end - def test_validates_exclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#generate_message passes the human_name of the model for interpolation" do + store_translations( + :errors => { :messages => { :foo => "You fooed: {{model}}." } }, + :models => { :topic => 'da topic' } + ) + assert_error_message 'You fooed: da topic.', :title, :foo end - # validates_numericality_of without :only_integer w/o mocha + test "#generate_message passes the human_name of the attribute for interpolation" do + store_translations( + :errors => { :messages => { :foo => "You fooed: {{attribute}}." } }, + :attributes => { :topic => { :title => 'da topic title' } } + ) + assert_error_message 'You fooed: da topic title.', :title, :foo + end - def test_validates_numericality_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + # generate_message will look up the key for the error message (e.g. :blank) in these namespaces: + # + # activerecord.errors.models.reply.attributes.title + # activerecord.errors.models.reply + # activerecord.errors.models.topic.attributes.title + # activerecord.errors.models.topic + # [default from class level :validates_foo statement if this is a String] + # activerecord.errors.messages - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end + test "#generate_message key fallbacks (given a String as key)" do + store_translations( + :errors => { + :models => { + :reply => { + :attributes => { :title => { :custom => 'activerecord.errors.models.reply.attributes.title.custom' } }, + :custom => 'activerecord.errors.models.reply.custom' + }, + :topic => { + :attributes => { :title => { :custom => 'activerecord.errors.models.topic.attributes.title.custom' } }, + :custom => 'activerecord.errors.models.topic.custom' + } + }, + :messages => { + :custom => 'activerecord.errors.messages.custom', + :kaputt => 'activerecord.errors.messages.kaputt' + } + } + ) - def test_validates_numericality_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + assert_error_message 'activerecord.errors.models.reply.attributes.title.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.reply.attributes.title.custom' - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) - end + assert_error_message 'activerecord.errors.models.reply.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.reply.custom' - # validates_numericality_of with :only_integer w/o mocha + assert_error_message 'activerecord.errors.models.topic.attributes.title.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.topic.attributes.title.custom' - def test_validates_numericality_of_only_integer_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + assert_error_message 'activerecord.errors.models.topic.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.models.topic.custom' - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end + assert_error_message 'activerecord.errors.messages.custom', :title, :kaputt, :message => 'custom' + delete_translation :'activerecord.errors.messages.custom' - def test_validates_numericality_of_only_integer_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + # Implementing this would clash with the AR default behaviour of using validates_foo :message => 'foo' + # as an untranslated string. I.e. at this point we can either fall back to the given string from the + # class-level macro (validates_*) or fall back to the default message for this validation type. + # assert_error_message 'activerecord.errors.messages.kaputt', :title, :kaputt, :message => 'custom' - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + assert_error_message 'custom', :title, :kaputt, :message => 'custom' end - # validates_numericality_of :odd w/o mocha + test "#generate_message key fallbacks (given a Symbol as key)" do + store_translations( + :errors => { + :models => { + :reply => { + :attributes => { :title => { :kaputt => 'activerecord.errors.models.reply.attributes.title.kaputt' } }, + :kaputt => 'activerecord.errors.models.reply.kaputt' + }, + :topic => { + :attributes => { :title => { :kaputt => 'activerecord.errors.models.topic.attributes.title.kaputt' } }, + :kaputt => 'activerecord.errors.models.topic.kaputt' + } + }, + :messages => { + :kaputt => 'activerecord.errors.messages.kaputt' + } + } + ) + + assert_error_message 'activerecord.errors.models.reply.attributes.title.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.reply.attributes.title.kaputt' - def test_validates_numericality_of_odd_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} + assert_error_message 'activerecord.errors.models.reply.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.reply.kaputt' - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) - end + assert_error_message 'activerecord.errors.models.topic.attributes.title.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.topic.attributes.title.kaputt' - def test_validates_numericality_of_odd_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} + assert_error_message 'activerecord.errors.models.topic.kaputt', :title, :kaputt + delete_translation :'activerecord.errors.models.topic.kaputt' - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + assert_error_message 'activerecord.errors.messages.kaputt', :title, :kaputt 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', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} + # full_messages - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal 'custom message', @topic.errors.on(:title) + test "#full_message with no format present" do + store_translations(:errors => { :messages => { :kaputt => 'is kaputt' } }) + assert_full_message 'Title is kaputt', :title, :kaputt end - def test_validates_numericality_of_less_than_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} + test "#full_message with a format present" do + store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :format => '{{attribute}}: {{message}}' } }) + assert_full_message 'Title: is kaputt', :title, :kaputt + end - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal 'global message', @topic.errors.on(:title) + test "#full_message with a type specific format present" do + store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :kaputt => '{{attribute}} {{message}}!' } }) + assert_full_message 'Title is kaputt!', :title, :kaputt end + test "#full_message with class-level specified custom message" do + store_translations(:errors => { :messages => { :broken => 'is kaputt' }, :full_messages => { :broken => '{{attribute}} {{message}}?!' } }) + assert_full_message 'Title is kaputt?!', :title, :kaputt, :message => :broken + end - # validates_associated w/o mocha + # switch locales - def test_validates_associated_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + test "#message allows to switch locales" do + store_translations(:en, :errors => { :messages => { :kaputt => 'is kaputt' } }) + store_translations(:de, :errors => { :messages => { :kaputt => 'ist kaputt' } }) + + assert_error_message 'is kaputt', :title, :kaputt + I18n.locale = :de + assert_error_message 'ist kaputt', :title, :kaputt + I18n.locale = :en + assert_error_message 'is kaputt', :title, :kaputt + end - Topic.validates_associated :replies - replied_topic.valid? - assert_equal 'custom message', replied_topic.errors.on(:replies) + test "#full_message allows to switch locales" do + store_translations(:en, :errors => { :messages => { :kaputt => 'is kaputt' } }, :attributes => { :topic => { :title => 'The title' } }) + store_translations(:de, :errors => { :messages => { :kaputt => 'ist kaputt' } }, :attributes => { :topic => { :title => 'Der Titel' } }) + + assert_full_message 'The title is kaputt', :title, :kaputt + I18n.locale = :de + assert_full_message 'Der Titel ist kaputt', :title, :kaputt + I18n.locale = :en + assert_full_message 'The title is kaputt', :title, :kaputt end +end - def test_validates_associated_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} +# ACTIVERECORD DEFAULT ERROR MESSAGES +# +# * test that Error generates the default error messages - Topic.validates_associated :replies - replied_topic.valid? - assert_equal 'global message', replied_topic.errors.on(:replies) +class ActiveRecordDefaultErrorMessagesI18nTests < ActiveSupport::TestCase + def assert_default_error_message(message, *args) + assert_equal message, error_message(*args) end - def test_validations_with_message_symbol_must_translate - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) + def error_message(*args) + ActiveRecord::Error.new(Topic.new, :title, *args).message end - def test_validates_with_message_symbol_must_translate_per_attribute - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) + # used by: validates_inclusion_of + test "default error message: inclusion" do + assert_default_error_message 'is not included in the list', :inclusion, :value => 'title' end - def test_validates_with_message_symbol_must_translate_per_model - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) + # used by: validates_exclusion_of + test "default error message: exclusion" do + assert_default_error_message 'is reserved', :exclusion, :value => 'title' end - def test_validates_with_message_string - Topic.validates_presence_of :title, :message => "I am a custom error" - @topic.title = nil - @topic.valid? - assert_equal "I am a custom error", @topic.errors.on(:title) + # used by: validates_associated and validates_format_of + test "default error message: invalid" do + assert_default_error_message 'is invalid', :invalid, :value => 'title' end -end - -class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase - def setup - reset_callbacks Topic - @topic = Topic.new - I18n.backend.store_translations :'en', { - :activerecord => { - :errors => { - :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 {{count}} characters)", - :too_short => "is too short (minimum is {{count}} characters)", - :wrong_length => "is the wrong length (should be {{count}} characters)", - :taken => "has already been taken", - :not_a_number => "is not a number", - :greater_than => "must be greater than {{count}}", - :greater_than_or_equal_to => "must be greater than or equal to {{count}}", - :equal_to => "must be equal to {{count}}", - :less_than => "must be less than {{count}}", - :less_than_or_equal_to => "must be less than or equal to {{count}}", - :odd => "must be odd", - :even => "must be even" - } - } - } - } + # used by: validates_confirmation_of + test "default error message: confirmation" do + assert_default_error_message "doesn't match confirmation", :confirmation, :default => nil end - def reset_callbacks(*models) - models.each do |model| - model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new) - end + # used by: validates_acceptance_of + test "default error message: accepted" do + assert_default_error_message "must be accepted", :accepted end - # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) - def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') + # used by: add_on_empty + test "default error message: empty" do + assert_default_error_message "can't be empty", :empty end - def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title') + # used by: add_on_blank + test "default error message: blank" do + assert_default_error_message "can't be blank", :blank end - # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) - def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') + # used by: validates_length_of + test "default error message: too_long" do + assert_default_error_message "is too long (maximum is 10 characters)", :too_long, :count => 10 end - def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') + # used by: validates_length_of + test "default error message: too_short" do + assert_default_error_message "is too short (minimum is 10 characters)", :too_short, :count => 10 end - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') + # used by: validates_length_of + test "default error message: wrong_length" do + assert_default_error_message "is the wrong length (should be 10 characters)", :wrong_length, :count => 10 end - def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title') + # used by: validates_uniqueness_of + test "default error message: taken" do + assert_default_error_message "has already been taken", :taken, :value => 'title' end - # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) - def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil) + # used by: validates_numericality_of + test "default error message: not_a_number" do + assert_default_error_message "is not a number", :not_a_number, :value => 'title' end - def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message') + # used by: validates_numericality_of + test "default error message: greater_than" do + assert_default_error_message "must be greater than 10", :greater_than, :value => 'title', :count => 10 end - # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) - def test_generate_message_accepted_with_default_message - assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil) + # used by: validates_numericality_of + test "default error message: greater_than_or_equal_to" do + assert_default_error_message "must be greater than or equal to 10", :greater_than_or_equal_to, :value => 'title', :count => 10 end - def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message') + # used by: validates_numericality_of + test "default error message: equal_to" do + assert_default_error_message "must be equal to 10", :equal_to, :value => 'title', :count => 10 end - # add_on_empty: generate_message(attr, :empty, :default => custom_message) - def test_generate_message_empty_with_default_message - assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil) + # used by: validates_numericality_of + test "default error message: less_than" do + assert_default_error_message "must be less than 10", :less_than, :value => 'title', :count => 10 end - def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message') + # used by: validates_numericality_of + test "default error message: less_than_or_equal_to" do + assert_default_error_message "must be less than or equal to 10", :less_than_or_equal_to, :value => 'title', :count => 10 end - # add_on_blank: generate_message(attr, :blank, :default => custom_message) - def test_generate_message_blank_with_default_message - assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil) + # used by: validates_numericality_of + test "default error message: odd" do + assert_default_error_message "must be odd", :odd, :value => 'title', :count => 10 end - def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message') + # used by: validates_numericality_of + test "default error message: even" do + assert_default_error_message "must be even", :even, :value => 'title', :count => 10 end - # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) - def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10) + test "custom message string interpolation" do + assert_equal 'custom message title', error_message(:invalid, :default => 'custom message {{value}}', :value => 'title') end +end - def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10) - end +# ACTIVERECORD VALIDATION ERROR MESSAGES - FULL STACK +# +# * test a few combinations full stack to ensure the tests above are correct - # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) - def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10) - end +class ActiveRecordValidationsI18nFullStackTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper - def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10) + def setup + reset_callbacks(Parrot, LiveParrot) + @old_backend, I18n.backend = I18n.backend, I18n::Backend::Simple.new + @parrot = LiveParrot.new end - # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) - def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) + def teardown + reset_callbacks(Parrot, LiveParrot) + I18n.backend = @old_backend end + + def assert_name_invalid(message) + yield + @parrot.valid? + assert_equal message, @parrot.errors.on(:name) + end + + # Symbols as class-level validation messages - def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10) + test "Symbol as class level validation message translated per attribute (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:live_parrot => {:attributes => {:name => {:broken => "is broken"}}}}} + Parrot.validates_presence_of :name, :message => :broken + end + end + + test "Symbol as class level validation message translated per attribute (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:parrot => {:attributes => {:name => {:broken => "is broken"}}}}} + Parrot.validates_presence_of :name, :message => :broken + end + end + + test "Symbol as class level validation message translated per model (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:live_parrot => {:broken => "is broken"}}} + Parrot.validates_presence_of :name, :message => :broken + end end - # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message]) - def test_generate_message_taken_with_default_message - assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title') + test "Symbol as class level validation message translated per model (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:parrot => {:broken => "is broken"}}} + Parrot.validates_presence_of :name, :message => :broken + end end - def test_generate_message_taken_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title') + test "Symbol as class level validation message translated as error message" do + assert_name_invalid("is broken") do + store_translations :errors => {:messages => {:broken => "is broken"}} + Parrot.validates_presence_of :name, :message => :broken + end end - # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) - def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') + # Strings as class-level validation messages + + test "String as class level validation message translated per attribute (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:live_parrot => {:attributes => {:name => {"is broken" => "is broken"}}}}} + Parrot.validates_presence_of :name, :message => "is broken" + end end - def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title') + test "String as class level validation message translated per attribute (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:parrot => {:attributes => {:name => {"is broken" => "is broken"}}}}} + Parrot.validates_presence_of :name, :message => "is broken" + end + end + + test "String as class level validation message translated per model (translation on child class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:live_parrot => {"is broken" => "is broken"}}} + Parrot.validates_presence_of :name, :message => "is broken" + end end - # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) - def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) + test "String as class level validation message translated per model (translation on base class)" do + assert_name_invalid("is broken") do + store_translations :errors => {:models => {:parrot => {"is broken" => "is broken"}}} + Parrot.validates_presence_of :name, :message => "is broken" + end end - def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) + test "String as class level validation message translated as error message" do + assert_name_invalid("is broken") do + store_translations :errors => {:messages => {"is broken" => "is broken"}} + Parrot.validates_presence_of :name, :message => "is broken" + end end - def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) + test "String as class level validation message not translated (uses message as default)" do + assert_name_invalid("is broken!") do + Parrot.validates_presence_of :name, :message => "is broken!" + end end +end - def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) +class ActiveRecordValidationsI18nFullMessagesFullStackTests < ActiveSupport::TestCase + include ActiveRecordValidationsI18nTestHelper + + def setup + reset_callbacks(Parrot, LiveParrot) + @old_backend, I18n.backend = I18n.backend, I18n::Backend::Simple.new + @parrot = LiveParrot.new end - def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) + def teardown + reset_callbacks(Parrot, LiveParrot) + I18n.backend = @old_backend + end + + def assert_full_message(message) + yield + @parrot.valid? + assert_equal message, @parrot.errors.full_messages.join end - def test_generate_message_odd_with_default_message - assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) + test "full_message format stored per custom error message key" do + assert_full_message("Name is broken!") do + store_translations :errors => { :messages => { :broken => 'is broken' }, :full_messages => { :broken => '{{attribute}} {{message}}!' } } + Parrot.validates_presence_of :name, :message => :broken + end end - def test_generate_message_even_with_default_message - assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) + test "full_message format stored per error type" do + assert_full_message("Name can't be blank!") do + store_translations :errors => { :full_messages => { :blank => '{{attribute}} {{message}}!' } } + Parrot.validates_presence_of :name + end end + test "full_message format stored as default" do + assert_full_message("Name: can't be blank") do + store_translations :errors => { :full_messages => { :format => '{{attribute}}: {{message}}' } } + Parrot.validates_presence_of :name + end + end end -- 1.6.0.5