validationのエラー表示順を直す(Railsのvalidate)

Railsの七不思議と言っていいけれどもvalidationのエラー表示がvalidateの記述順と一致しない問題がある。で毎回id:elm200さんのサイトなどを参考にパッチを当てることになる。不毛。外人さんはエラー表示順とか気にしないんだろうかとか妙な疑問を持ったり。


技術的にはActiveRecord::Errorsの@errorsがHashなので順番が保証されないという原因。これをOrderedHashにすることでエラー表示順が保証される。この件についてa_matsudaさんが本家Railsにパッチを送った。期待上げ!

http://rails.lighthouseapp.com/projects/8994/tickets/2301

Ensure AR validation errors to be ordered in declared order

AR#errors sometimes returns error messages in unexpected order.

I wrote a patch to use OrdererHash for AR#errors in order to generate nicely ordered error messages even in good old Ruby 1.8.


From e78f2f569fe433bf813fecdad67afd3f0ddd2dc8 Mon Sep 17 00:00:00 2001
From: Akira Matsuda
Date: Thu, 19 Mar 2009 14:40:42 +0900
Subject: [PATCH 1/2] Fix default_error_messages back to the original message

---
activerecord/test/cases/validations_test.rb | 9 +++++++--
1 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index c20f5ae..b2d1c43 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -895,14 +895,19 @@ class ValidationsTest < ActiveRecord::TestCase
end

def test_validates_length_with_globally_modified_error_message
- ActiveSupport::Deprecation.silence do
- ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre {{count}}'
+ original_message = ActiveSupport::Deprecation.silence do
+ returning ActiveRecord::Errors.default_error_messages[:too_short] do
+ ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre {{count}}'
+ end
end
Topic.validates_length_of :title, :minimum => 10
t = Topic.create(:title => 'too short')
assert !t.valid?

assert_equal 'tu est trops petit hombre 10', t.errors['title']
+ ActiveSupport::Deprecation.silence do
+ ActiveRecord::Errors.default_error_messages[:too_short] = original_message
+ end
end

def test_validates_size_of_association
--
1.6.2


From cb4dab1008fa739cb6eebace4948833ba5419e50 Mon Sep 17 00:00:00 2001
From: Akira Matsuda
Date: Thu, 19 Mar 2009 14:42:08 +0900
Subject: [PATCH 2/2] Ensure validation errors to be ordered in declared order

---
activerecord/lib/active_record/validations.rb | 5 +++--
activerecord/test/cases/validations_test.rb | 22 ++++++++++++++++------
2 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index d2d12b8..93cfb0f 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -27,7 +27,8 @@ module ActiveRecord
end

def initialize(base) # :nodoc:
- @base, @errors = base, {}
+ @base = base
+ clear
end

# Adds an error to the base object instead of any particular attribute. This is used
@@ -218,7 +219,7 @@ module ActiveRecord

# Removes all errors that have been added.
def clear
- @errors = {}
+ @errors = ActiveSupport::OrderedHash.new
end

# Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index b2d1c43..2a4762a 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -1427,12 +1427,22 @@ class ValidationsTest < ActiveRecord::TestCase
end

def test_validation_order
- Topic.validates_presence_of :title
- Topic.validates_length_of :title, :minimum => 2
-
- t = Topic.new("title" => "")
- assert !t.valid?
- assert_equal "can't be blank", t.errors.on("title").first
+ Topic.validates_presence_of :title, :author_name
+ Topic.validate {|topic| topic.errors.add('author_email_address', 'will never be valid')}
+ Topic.validates_length_of :title, :content, :minimum => 2
+
+ t = Topic.new :title => ''
+ t.valid?
+ e = t.errors.instance_variable_get '@errors'
+ assert_equal 'title', key = e.keys.first
+ assert_equal "can't be blank", t.errors.on(key).first
+ assert_equal 'is too short (minimum is 2 characters)', t.errors.on(key).second
+ assert_equal 'author_name', key = e.keys.second
+ assert_equal "can't be blank", t.errors.on(key)
+ assert_equal 'author_email_address', key = e.keys.third
+ assert_equal 'will never be valid', t.errors.on(key)
+ assert_equal 'content', key = e.keys.fourth
+ assert_equal 'is too short (minimum is 2 characters)', t.errors.on(key)
end

def test_invalid_should_be_the_opposite_of_valid

1.6.2