From ef78dc6b9505f98c53ebd011a973293ab1bba26a Mon Sep 17 00:00:00 2001 From: John D. Hume Date: Tue, 3 Jun 2008 12:13:27 -0400 Subject: [PATCH] Format validation error messages with the value that failed. Add examples to the docs for affected validates_*_of methods. --- activerecord/lib/active_record/validations.rb | 40 +++++++------ activerecord/test/cases/validations_test.rb | 80 ++++++++++++++++++------- 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index c97aafb..5a55e23 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -482,6 +482,7 @@ module ActiveRecord # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character" # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me." + # validates_length_of :job_title, :minimum=>20, :message=>"An impressive title must be at least %d characters. '%s' is too short." # end # # Configuration options: @@ -532,15 +533,12 @@ module ActiveRecord when :within, :in raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range) - too_short = options[:too_short] % option_value.begin - too_long = options[:too_long] % option_value.end - validates_each(attrs, options) do |record, attr, value| value = value.split(//) if value.kind_of?(String) if value.nil? or value.size < option_value.begin - record.errors.add(attr, too_short) + record.errors.add(attr, options[:too_short] % [option_value.begin, value]) elsif value.size > option_value.end - record.errors.add(attr, too_long) + record.errors.add(attr, options[:too_long] % [option_value.end, value]) end end when :is, :minimum, :maximum @@ -550,11 +548,11 @@ module ActiveRecord validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" } message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long } - message = (options[:message] || options[message_options[option]]) % option_value - validates_each(attrs, options) do |record, attr, value| value = value.split(//) if value.kind_of?(String) - record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value] + unless !value.nil? and value.size.method(validity_checks[option])[option_value] + record.errors.add(attr, (options[:message] || options[message_options[option]]) % [option_value, value]) + end end end end @@ -566,10 +564,10 @@ module ActiveRecord # can be named "davidhh". # # class Person < ActiveRecord::Base - # validates_uniqueness_of :user_name, :scope => :account_id + # validates_uniqueness_of :user_name, :message => "'%s' is already in the system" # end # - # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example, + # It can also validate whether the value of the specified attributes are unique based on one or more scope parameters. For example, # making sure that a teacher can only be on the schedule once per semester for a particular class. # # class TeacherSchedule < ActiveRecord::Base @@ -656,7 +654,7 @@ module ActiveRecord found = results.any? { |a| a[attr_name.to_s] == value } end - record.errors.add(attr_name, configuration[:message]) if found + record.errors.add(attr_name, configuration[:message] % value) if found end end end @@ -667,6 +665,7 @@ module ActiveRecord # # class Person < ActiveRecord::Base # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create + # validates_format_of :username, :with => /^[a-z]+$/, :message => "%s is no good" # end # # Note: use \A and \Z to match the start and end of the string, ^ and $ match the start/end of a line. @@ -807,6 +806,7 @@ module ActiveRecord # # class Person < ActiveRecord::Base # validates_numericality_of :value, :on => :create + # validates_numericality_of :age, :greater_than_or_equal_to => 18, :message => "must be at least %d. %s is too young." # end # # Configuration options: @@ -845,15 +845,17 @@ module ActiveRecord if configuration[:only_integer] unless raw_value.to_s =~ /\A[+-]?\d+\Z/ - record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number]) + message = (configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number]) % raw_value + record.errors.add(attr_name, message) next end - raw_value = raw_value.to_i + numeric_value = raw_value.to_i else begin - raw_value = Kernel.Float(raw_value.to_s) + numeric_value = Kernel.Float(raw_value.to_s) rescue ArgumentError, TypeError - record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number]) + message = (configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number]) % raw_value + record.errors.add(attr_name, message) next end end @@ -861,11 +863,13 @@ module ActiveRecord numericality_options.each do |option| case option when :odd, :even - record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[] + unless numeric_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[] + record.errors.add(attr_name, (configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) % raw_value) + end else message = configuration[:message] || ActiveRecord::Errors.default_error_messages[option] - message = message % configuration[option] if configuration[option] - record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]] + message = message % [configuration[option], raw_value] + record.errors.add(attr_name, message) unless numeric_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]] end end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 7b71647..1101ae6 100755 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -530,6 +530,14 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal "has already been taken", w6.errors.on(:city), "Should have uniqueness message for city" end + def test_validate_uniqueness_with_formatted_message + Topic.validates_uniqueness_of :title, :message => "the title '%s' is already used" + Topic.create! :title => 'War and Peace' + + t = Topic.create :title => 'War and Peace' + assert_equal "the title 'War and Peace' is already used", t.errors.on(:title) + end + def test_validate_format Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data") @@ -811,6 +819,15 @@ class ValidationsTest < ActiveRecord::TestCase assert t.save end + def test_validates_length_of_with_message_formatting_using_within + Topic.validates_length_of :title, :content, :within => 3..5, + :too_short => "at least %s chars, unlike '%s'", :too_long => "at most %s chars, unlike '%s'" + + t = Topic.create("title" => "a!", "content" => "I'm ooooooooh so very long") + assert_equal "at least 3 chars, unlike 'a!'", t.errors.on(:title) + assert_equal "at most 5 chars, unlike 'I'm ooooooooh so very long'", t.errors.on(:content) + end + def test_validates_length_of_using_is Topic.validates_length_of :title, :is => 5 @@ -896,51 +913,51 @@ class ValidationsTest < ActiveRecord::TestCase end def test_validates_length_of_custom_errors_for_minimum_with_message - Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d" ) + Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d on %s" ) t = Topic.create("title" => "uhoh", "content" => "whatever") assert !t.valid? assert t.errors.on(:title) - assert_equal "boo 5", t.errors["title"] + assert_equal "boo 5 on uhoh", t.errors["title"] end def test_validates_length_of_custom_errors_for_minimum_with_too_short - Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d" ) + Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d on %s" ) t = Topic.create("title" => "uhoh", "content" => "whatever") assert !t.valid? assert t.errors.on(:title) - assert_equal "hoo 5", t.errors["title"] + assert_equal "hoo 5 on uhoh", t.errors["title"] end def test_validates_length_of_custom_errors_for_maximum_with_message - Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d" ) + Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d on %s" ) t = Topic.create("title" => "uhohuhoh", "content" => "whatever") assert !t.valid? assert t.errors.on(:title) - assert_equal "boo 5", t.errors["title"] + assert_equal "boo 5 on uhohuhoh", t.errors["title"] end def test_validates_length_of_custom_errors_for_maximum_with_too_long - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d" ) + Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d on %s" ) t = Topic.create("title" => "uhohuhoh", "content" => "whatever") assert !t.valid? assert t.errors.on(:title) - assert_equal "hoo 5", t.errors["title"] + assert_equal "hoo 5 on uhohuhoh", t.errors["title"] end def test_validates_length_of_custom_errors_for_is_with_message - Topic.validates_length_of( :title, :is=>5, :message=>"boo %d" ) + Topic.validates_length_of( :title, :is=>5, :message=>"boo %d on %s" ) t = Topic.create("title" => "uhohuhoh", "content" => "whatever") assert !t.valid? assert t.errors.on(:title) - assert_equal "boo 5", t.errors["title"] + assert_equal "boo 5 on uhohuhoh", t.errors["title"] end def test_validates_length_of_custom_errors_for_is_with_wrong_length - Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d" ) + Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d on %s" ) t = Topic.create("title" => "uhohuhoh", "content" => "whatever") assert !t.valid? assert t.errors.on(:title) - assert_equal "hoo 5", t.errors["title"] + assert_equal "hoo 5 on uhohuhoh", t.errors["title"] end def test_validates_length_of_using_minimum_utf8 @@ -1470,18 +1487,39 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase valid!([2]) end - def test_validates_numericality_with_numeric_message - Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %d" - topic = Topic.new("title" => "numeric test", "approved" => 10) + def test_validates_numericality_with_formatted_message + Topic.validates_numericality_of :approved, :message => "can't use %s" + topic = Topic.create("approved" => 'one') - assert !topic.valid? - assert_equal "smaller than 4", topic.errors.on(:approved) + assert_equal "can't use one", topic.errors.on(:approved) + end + + def test_validates_numericality_with_formatted_message_with_only_integer + Topic.validates_numericality_of :approved, :only_integer => true, :message => "can't use %s" + topic = Topic.create("approved" => 1.5) + + assert_equal "can't use 1.5", topic.errors.on(:approved) + end + + def test_validates_numericality_with_formatted_message_with_less_than + Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %d, you said %s" + topic = Topic.create("approved" => 10) + + assert_equal "smaller than 4, you said 10", topic.errors.on(:approved) + end + + def test_validates_numericality_with_formatted_message_with_greater_than + Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %d, you said %s" + topic = Topic.create("approved" => 1) + + assert_equal "greater than 4, you said 1", topic.errors.on(:approved) + end - Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %d" - topic = Topic.new("title" => "numeric test", "approved" => 1) + def test_validates_numericality_with_formatted_message_with_even + Topic.validates_numericality_of :approved, :even => true, :message => "must be even, you said %s" + topic = Topic.create("approved" => 1) - assert !topic.valid? - assert_equal "greater than 4", topic.errors.on(:approved) + assert_equal "must be even, you said 1", topic.errors.on(:approved) end private -- 1.5.3