Rails 2.3.2のvalidates_uniqueness_ofの不具合について
Rails 2.3.2がリリースされたので、さっそく2.2.2からアップデートしてみた。
特に問題ないなと思っていたら、validates_uniqueness_ofでエラーが発生したので対処方法をメモ。
どんなエラーなのか
Railsには重複をチェックするためのバリデーションとしてvalidates_uniqueness_ofが用意されている。
これを日本語のようにマルチバイトな文字列を値とするカラムを指定すると、
SQLの部分でエラーとなるケースがある。
エラー再現コード
-- app/models/profile.rb -- class Profile < ActiveRecord::Base validates_uniqueness_of :nickname end profile = Profile.new(:nickname => 'サンプルユーザ') profile.valid? ここでエラー => : SELECT "profiles".id FROM "profiles" WHERE ("profiles"."nickname" = E'サンプルユー・') LIMIT 1
原因
明らかにコレだろ、jk。
-- $RUBY_HOME/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/validations.rb -- def validates_uniqueness_of(*attr_names) ・・・ value = column.limit ? value.to_s[0, column.limit] : value.to_s ・・・ end
どうしてこのようになってしまうのかは、以下の例を見てもらえれば分かるだろう。
単純にvalue[0, limit]としてしまうと、文字列として正しく扱えない。
irb(main):001:0> $KCODE = 'u' irb(main):002:0> require 'jcode' irb(main):003:0> value = 'サンプルユーザ' irb(main):004:0> limit = 20 irb(main):005:0> value = limit ? value.to_s[0, limit] : value.to_s => "サンプルユー・ ← 文字化けするにしても、"サンプルユー・"とならないといけない irb(main):006:0> value = 'サンプルユーザ' irb(main):007:0> value = limit ? value.to_s.split(//)[0, limit].join : value.to_s => "サンプルユーザ"
対処方法
validates_uniqueness_ofの内容をまるまるコピーして、lib/validates_uniqueness_of.rbとかに置く。
そしたら、config/environments.rbの最後あたりにrequire 'validates_uniqueness_of.rb'しておく。
で、問題の箇所をこう直す。
(ちなみにvalidates_uniqueness_offの定義は$RUBY_HOME/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/validations.rbにある)
-- #{RAILS_ROOT}/lib/validates_uniqueness_of.rb -- module ActiveRecord module Validations module ClassMethods def validates_uniqueness_of(*attr_names) ・・・省略・・・ if value.nil? comparison_operator = "IS ?" elsif column.text? comparison_operator = "#{connection.case_sensitive_equality_operator} ?" #value = column.limit ? value.to_s[0, column.limit] : value.to_s value = column.limit ? value.to_s.split(//)[0, column.limit].join : value.to_s else comparison_operator = "= ?" end ・・・省略・・・ end end # EOF ClassMethods end # EOF Validations end # EOF ActiveRecord