Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

wrong message used in validates_length_of

#5283

If both :too_short and :too_long messages are defined for validates_length_of, it uses the :too_short message even when the value is too long.

With such model:

class User < ActiveRecord::Base
  validates_length_of :username, :minimum => 3, :maximum => 10, :too_short => 'too short', :too_long => 'too long'
end

This is what you get:

u = User.new :username => 'js'
u.valid?
u.errors  # => {:username=>["too short"]} 

u = User.new :username => 'jakub_suder'
u.valid?
u.errors  # => {:username=>["too short"]}

The reason is probably this line in validate_each method in length.rb:

options[:message] ||= default_message if default_message

Once it passes through the too_short validation (even though it doesn't add the error in the end, because valid_value returns true), options[:message] is set and it's not changed again when it comes to too_long validation.

It was probably broken in this commit: http://github.com/rails/rails/commit/26392c4ac57e27c63984d47c6326c1... (there might be other validations that have been broken this way, though this one is unique because it has two different messages and most validations have just one).

Reported by Jakub Suder · August 3rd, 2010 @ 10:51 AM

State: resolved
Milestone: 3.0.2
Assigned to: José Valim José Valim
Importance: High

Activity

  1. Rohit Arondekar
    Rohit Arondekar
    • Milestone cleared.
    • State changed from new to open
    • Assigned user set to Santiago Pastorino
    • Importance changed from to Low

    Confirmed.

    Attached is a failing test.

    August 3rd, 2010 @ 12:01 PM

  2. Rohit Arondekar
    Rohit Arondekar

    Sorry, forgot to attach the patch in previous comment.

    August 3rd, 2010 @ 12:02 PM

  3. Rohit Arondekar
    Rohit Arondekar

    Ok I have some weird findings to report.

    First, the test that I provided in the patch above is as follows:

      def test_validates_length_of_custom_errors_for_both_too_short_and_too_long
        Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long'
    
        t = Topic.new(:title => 'a')
        assert t.invalid?
        assert t.errors[:title].any?
        assert_equal ['too short'], t.errors['title']
    
        t = Topic.new(:title => 'aaaaaa')
        assert t.invalid?
        assert t.errors[:title].any?
        assert_equal ['too long'], t.errors['title']
      end
    

    The part of the code in question in length.rb inside AMo is as follows:

          def validate_each(record, attribute, value)
            value = options[:tokenizer].call(value) if value.kind_of?(String)
    
            CHECKS.each do |key, validity_check|
              next unless check_value = options[key]
              default_message = options[MESSAGES[key]]
              options[:message] ||= default_message if default_message
    
              valid_value = if key == :maximum
                value.nil? || value.size.send(validity_check, check_value)
              else
                value && value.size.send(validity_check, check_value)
              end
    
              next if valid_value
    
              record.errors.add(attribute, MESSAGES[key],
                                options.except(*RESERVED_OPTIONS).merge!(:count => check_value))
            end
          end
    

    Note that default_message is set too early, it should be set only if an invalid value is found, i.e after next if valid_value as follows:

          def validate_each(record, attribute, value)
            value = options[:tokenizer].call(value) if value.kind_of?(String)
    
            CHECKS.each do |key, validity_check|
              next unless check_value = options[key]
    
              valid_value = if key == :maximum
                value.nil? || value.size.send(validity_check, check_value)
              else
                value && value.size.send(validity_check, check_value)
              end
    
              next if valid_value
    
    puts options[:message]
    
              default_message = options[MESSAGES[key]]
              options[:message] ||= default_message if default_message
    
    puts "#{value} is #{valid_value} #{key} and #{options[:message]} but message should be #{options[MESSAGES[key]]}"
    
              record.errors.add(attribute, MESSAGES[key],
                                options.except(*RESERVED_OPTIONS).merge!(:count => check_value))
            end
          end
    

    Note that I've added two puts statements. Now if I run my test on this changed code, the test still fails with the original problem and more interestingly I get the following output (truncated):

    ["a"] is false minimum and too short but message should be too short
    too short
    ["a", "a", "a", "a", "a", "a"] is false maximum and too short but message should be too long
    

    Note that in the second run, for testing :too_long message, options[:message] is set to too short. (see second line in above output) How can that be?

    Also reversing the order of my test, that is testing for :too_long first and then :too_short, causes the validation message set to 'too long' instead of 'too short'.

    Running the two sets of asserts in isolation works, if this the desired effect I can provide a patch with the above code fix. However if my test should pass then the problem could be deeper.

    August 3rd, 2010 @ 01:42 PM

  4. José Valim
    José Valim
    • Assigned user changed from Santiago Pastorino to José Valim
    • Importance changed from Low to High

    August 3rd, 2010 @ 01:51 PM

  5. Repository
  6. Repository
  7. Repository
  8. Repository
    Repository
    • State changed from resolved to open

    (from [257e9c4ec46c5045785081e3989bed9bc8d9b37a]) Failing test for validates_length_of, when both too_short and too_long messages are set [#5283 wrong message used in validates_length_of state:open]

    Signed-off-by: José Valim jose.valim@gmail.com
    http://github.com/rails/rails/commit/257e9c4ec46c5045785081e3989bed...

    August 3rd, 2010 @ 02:37 PM

  9. José Valim
    José Valim
    • State changed from open to resolved

    August 3rd, 2010 @ 02:39 PM

  10. Jakub Suder
    Jakub Suder

    Wow, that was fast. Thanks José & Rohit :)

    August 3rd, 2010 @ 06:48 PM

  11. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 9th, 2010 @ 09:47 PM

  12. Jeremy Kemper
  13. bingbing