フォームの確認ページとvalidates_length_of

■フォーム入力した後の確認ページを作成する方法を考える
色々方法はあるが、今回はセッションを使う方法を考える。
もっといい方法はこちら


作戦はこうだ。


・フォームからパラメータを受ける
・検証を行なう
・セッションに値を入れる
・確認ページを表示する
・コミット側ではセッションから値を取り出す
・DBへ反映
・セッションのクリア


これを単純にコードにすると次のようになる。


■Controller

   def edit
    @user = get_user_from_session #現在ユーザー

    #getリクエストの場合はその人の現在の値を、postできた場合は編集中とみなし。
    #そのデータをフォームに入れる
    @profile = (request.get? ? @user.profile : session[:profile_data])
  end
  
  def edit_confirmation
    @user = get_user_from_session
    @profile = Profile.new(params[:profile])
    
    if @profile.valid?
      session[:profile_data] = @profile
    else
      render :action => "edit_profile"
    end
  end
  
  def update_profile
    begin
    
     if (@profile = session[:profile_data]) && (@user = get_user_from_session)
       @user.profile = @rprofile
       session_clear(:profile_data)  #セッションをクリア
       redirect_to :action =>  "result_update_profile"
     else
       raise
     end
    
    rescue => e
      emergency_action(get_user_from_session) #エラーメッセージページへ
    end
  end

さて、これで、なんとか確認ページつきフォームを作成できた。
では、このProfileモデルに検証機能をつけるとしよう。フォームなので、
文字数制限がほしいと考える。


ここで思いつくのが次のようなコードだろう。

  validates_length_of :hobbies, :maximum => 50, :too_long => "文字数制限オーバー"

しかし、これではうまくいかない場合がある。(もちろん他の方法でもできる)。
それは次のような場合だ。

 UserとProfileは最初に登録付けておきたい。

つまり、UserとProfileの登録時期には、ずれがあり、たとえば、アカウント情報は
先に登録し、プロフィールを後から編集するような場合。
その際には、ユーザー登録時に次のようなコードが入る。

  if @user.save
    ***
    @user.profile = Profile.new
    ***
  end

このとき、Profileにフォームのlengthの検証があると、ひっかかかる。
それは、validates_length_ofはnilをエラーにするからである。
本来フォームからのデータは空欄のときは""になるので特に問題はないのである。
http://railsapi.masuidrive.jp/module/ActiveRecord::Validations::ClassMethods/validates_length_of
よって、次のようなifオプションを使うだろう。

  validates_length_of :hobbies, :maximum => 100, :if => !self.new_record?

これを書けば、作成時には検証されない。
しかし、これでは、フォームの確認時にも検証されないことになってしまう。
何故なら、Profile.newを使うからである。もし、ここでProfile.findを使っても、
直前のフォームからの値は反映されていない値になってしまう。よって、ここでは
newを使うのが自然だろう。


では、どうすれば検証できるのか。その1つの方法が、nilを通すvalidates_length_ofを作る。
ことである。よって、次のようなコードを考える。

  def validate
     hobbies_valid?
  end

private
  
  def hobbies_valid?
    if MAX_HOBBIES_SIZE < (hobbies || "").size 
      errors/add(:hobbies,"が文字数制限を超えています。")
      return false
    end
    true
  end

このような検証メソッドを作成し、これは特にnewだから検証しないという制限をつけない。
これなら、nilは0文字とみなすためnilでも通すことができる。


しかし、もしフォームがたくさんあったら、**_valid?を沢山作るべきか。それは、
なんとなくストレスを感じる。
対象パラメータとその長さのMAXを比較するという抽象レベルではやっていることが
同じだからである。


よって、次のようなメソッドを考えた。(変数の名前がよくない)

  def validate
    def_length_attrs = ["MAX_COMPANY_NAME_LENGTH","MAX_HOBBIES_LENGTH",・・・]    
    def_length_attrs.each{|def_attr_length|validate_length(def_attr_length)}
  end
 
private
  def validate_length(def_attr_length)
    attr_symbol = get_attr_symbol_from_attr_length(def_attr_length)
    if eval(def_attr_length) < (send(attr_symbol) || "").size
      errors.add(attr_symbol,"が文字数制限を超えています。")
      return false
    end
    return true
  end

  def get_attr_symbol_from_attr_length(def_attr_length)
    attr_name = def_attr_length.dup
    attr_name.sub!(/MAX_/,"")
    attr_name.sub!(/_LENGTH/,"")
    attr_name.downcase!
    attr_name.intern
  end

文字数リミットを定義した変数を渡すと、そこから属性名を引き出すようになっている。
よって、定義定数はMAX_属性名の大文字_LENGTHにする規約。