rails/validatesでbefore_type_cast のバックアップソース(No.1)
更新[[公開メモ]] * validates で type cast される前の値を検証したい [#rf63f1c9] params で得たフォームデータは、 ActiveRecord へ代入される際に ActiveRecord の各 columns のデータ型にキャストされます。 例えば record.birthday がデータベース上で datetime 型の時、 params[:record][:birthday] の文字データは ruby の DateTime 方に変換されてから record.birthday に代入されます。 これは期待通りの結果と言えるのですが、 validates で指定した検証は通常、すでに DateTime 型に変換され、record.birthday へ代入された値に対して呼び出されます。 ですので、varidates で record.birthday に対して DateTime 型のメソッドを呼び出して検証するなどが可能になるわけです。 しかし、コントローラのロジックとしては、DateTime 型へ変換される前の、 Web Form で入力される文字列に対して検証を行いたい場合があります。 例えば、Web Form で birthday の入力が任意であったとします。 このとき該当の input 要素が空であれば、 予定通り record.birthday には nil が入力されて、問題ありません。 しかし、DateTime へ変換できない形式の文字列であった場合にも、 type cast 後の record.birthday は nil になってしまうため、 まったく文字列が入力されなかったのと見分けが付きません。 * (attribute)_before_type_cast [#i013c0ad] こういう時のために、ActiveRecord では record.birthday と同時に、 record.birthday_before_type_cast という属性が自動的に定義されます。 http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/BeforeTypeCast.html これを使うと、例えば LANG:ruby validates :birthday_before_type_cast, format: { with: /\A(2014)-([01]\d)-([0-3]\d)\z/ }, unless: ->(rec){ rec.birthday_before_type_cast.blank? } などとして、type cast 前の値で検証を行えます。 * エラーメッセージの問題 [#bf54ddb8] ただし上記のコードでは、検証失敗時に表示されるメッセージは、 Birthday before type cast is invalid 等となってしまい、ユーザーに提示するメッセージとしては不適切な物になってしまいます。 * エラー表示の問題 [#lcd24791] エラーが起きた input 要素をマークする際にも、 not @record.errors[:birthday].empty? だけではエラーのあるなしを判別できず、 not @record.errors[:birthday].empty? or not @record.errors[:birthday_before_type_cast].empty? などとしなければならず、面倒きわまりないです。 * ActiveRecord::Errors へのパッチ [#u6a430e8] この問題への対処としては、ActiveRecord::Errors::add において エラーメッセージを errors へ登録する際に、attribute の末尾が _before_type_cast であればその部分を削ってしまうのが良いように思いました。 ActiveRecord のソースに直接パッチを当てるのであれば、 https://github.com/rails/rails/blob/b97035df64f5b2f912425c4a7fcb6e6bb3ddab8d/activemodel/lib/active_model/errors.rb#L291 のあたりになりますが・・・ ちょっとそれも嫌だったので、とりあえず以下のモジュールを include してしのぐことにしました。 LANG:ruby(linenumber) module ActiveModelErrorsAddWithRemovingBeforeTypeCastExtention def self.included(mod) mod.class_eval do alias_method_chain :add, :removing_before_type_cast end end def add_with_removing_before_type_cast(attribute, message = :invalid, options = {}) if attribute.to_s =~ /(.*)_before_type_cast\z/ stripped = $1.to_sym attribute = stripped if attributes.has_key? stripped end add_without_removing_before_type_cast(attribute, message, options) end end ActiveModel::Errors.class_eval do include ActiveModelErrorsAddWithRemovingBeforeTypeCastExtention end もっと良い方法もありそうですが・・・ もしあれば教えてください。。。 * 質問・コメント [#wad1a837] #article_kcaptcha
Counter: 12163 (from 2010/06/03),
today: 1,
yesterday: 3