バリデーション(2)

バリデーションでエラーになったときにどうやって値が引き継がれるか調べてみたメモ。結論から言うとポストされてきた値を使ってポスト前のフォームをrenderするとよい。

samples_controller.rb

プロジェクトは前回のものと同じ。理解しやすくするため、コントローラのrespond_toからhtml対応部分のみ抜き出したものを作成。各メソッドの先頭にデバッグ出力を追加。

require 'pp'

class SamplesController < ApplicationController
  # GET /samples
  def index
    pp 'index', params
    @samples = Sample.find(:all)
  end

  # GET /samples/1
  def show
    pp 'show', params
    @sample = Sample.find(params[:id])
  end

  # GET /samples/new
  def new
    pp 'new', params
    @sample = Sample.new
  end

  # GET /samples/1/edit
  def edit
    pp 'edit', params
    @sample = Sample.find(params[:id])
  end

  # POST /samples
  def create
    pp 'create', params
    @sample = Sample.new(params[:sample])

    if @sample.save
      flash[:notice] = 'Sample was successfully created.'
      redirect_to(@sample)
    else
      flash[:notice] = 'Sample was not created.'
      render :action => "new"
    end
  end

  # PUT /samples/1
  def update
    pp 'update', params
    @sample = Sample.find(params[:id])

    if @sample.update_attributes(params[:sample])
      flash[:notice] = 'Sample was successfully updated.'
      redirect_to(@sample)
    else
      flash[:notice] = 'Sample was not updated.'
      render :action => "edit"
    end
  end

  # DELETE /samples/1
  def destroy
    pp 'destroy', params
    
    @sample = Sample.find(params[:id])
    @sample.destroy

    redirect_to(samples_url)
  end
end

新規作成の流れは

  • GET index # index.html.erb
  • GET new # new.html.erb
  • POST create
    • (成功したとき) redirect_to(@sample) # GET show
    • (失敗したとき) render(:action => "new") # new.html.erb

編集の流れは

  • GET index # index.html.erb
  • GET edit # edit.html.erb
  • POST update
    • (成功したとき) redirect_to(@sample) # GET show
    • (失敗したとき) render(:action => "edit") # edit.html.erb

となっていて、失敗パターンで使われるテンプレートはポスト前と同じもの。scaffoldなのでこのあたりは全く意識しなくても動作するが、独自にコントローラを作成する場合はバリデーションを考慮してテンプレートに値を引き渡す必要がある。

ActionView::Helpers::FormHelper.form_for

テンプレート側として form_for の流れを確認しておく。

      def form_for(record_or_name_or_array, *args, &proc)
        raise ArgumentError, "Missing block" unless block_given?
        # 引数からオプションHashを抜き出す
        options = args.extract_options!
        # 第1引数を解析
        case record_or_name_or_array
        when String, Symbol
          # :sample と書かれた場合
          object_name = record_or_name_or_array
        when Array
          # [:sample, @sample] と書かれた場合
          object = record_or_name_or_array.last
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
          apply_form_for_options!(record_or_name_or_array, options)
          args.unshift object
        else
          # @sample と書かれた場合
          object = record_or_name_or_array
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
          apply_form_for_options!([object], options)
          args.unshift object
        end
        # ブロックのコンテキストでフォームタグを追加
        concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
        # フィールドの処理。引数は :sample, @sample, *args, option のようになる。
        fields_for(object_name, *(args << options), &proc)
        concat('</form>', proc.binding)
      end

fields_for で ActionView::Helpers::FormTagHelper にあるメソッドが呼ばれることになる。このため、引数の順序が調整されている。

では、実際に fields_for でどのようなタグが生成されるか・・・特に独自コントローラでActiveRecordを使ってないような場合にどのようにするとよいのかを調べたかったが、残念。今日はここまで。