Matthewの備忘録

忘れたときはここを見ろ。何か書いてある。

RoRでDBテーブルのカラムと一致しない項目がある入力フォームを実現する その2

 前回のままでは実用的ではないので、ストロングパラメーターとバリデーション、そしてi18nについて記しておく。
参考:
matthew.hatenadiary.jp

ストロングパラメーター

 
 ストロングパラメーターの振る舞いについては次の記事が詳しいので見て欲しい。

参考(詳細に解説している):
www.transnet.ne.jp

 ようするに、エンティティクラス/オブジェクトの属性と同名のキーを持つパラメーターのうち指定のパラメーターが受け渡されたかをチェックする。その後にパラメーターで構築したハッシュを返すので、エンティティオブジェクトを生成や属性の更新が容易に行えるコードが書ける。例えば、前回のようにテーブル属性は二つあるが、入力フィールドが一つの場合、nameの代わりにname_formを使って二つ分の入力がされる一つのフィールドのパラメーター構造のチェックをしたい場合は、単純にそのシンボルとパラメーター名に変更するだけでよい。

    def name_params
      # params.require(:name).permit(:family_name, :first_name)
      params.require(:name_form).permit(:name)
    end

 本当はGitHub上のRoRソースを追いかけてその構造と挙動を掴んでおきたいところだが、後日にする。

参考;
rails/strong_parameters.rb at afb41fbefa509cea1259eafb28a0b7825688464b · rails/rails · GitHub

バリデーションとエラー処理

バリデーション機能のインクルードと定義

 DBテーブルと直結するモデルに情報を渡す前にフォーム専用のモデルでもバリデーションを行い、エラー処理をしたい。これは簡単に実装することが可能である。ActiveModel::Validationsをインクロードした後に、一般のモデルと同様にvalidatesを定義しておけばよい。そして任意のタイミングでバリデーションを実行すればよい。次はpresenceを指定して空入力をさせない例である:

  include ActiveModel::Validations

  validates :name, :presence => { :message => "入力してね" }

エラー処理

 パラメーターに異常、またはDBテーブルと直結するモデルにエラーが出ればコントローラーでエラー対応しなくてはならないが、フォーム用に作ったモデルオブジェクトとそのモデルとはコントローラーで手短に処理できるほどの関連が設定されていないので、エラー処理は少し考慮して書かないといけない。自動的ににカスケーディング処理がされればよいのだが、次のように書いておくべきだろう:

# newから遷移後のcreate
  def create
    @name = NameForm.new(name: params[:name_form][:name])
    respond_to do |format|
      # if @name.save
      if @name.valid? then # and name.save then
        family, first = @name.name.to_s.split(' ')
        name = Name.create(family_name: family, first_name: first)
        if name.save then
          # format.html { redirect_to @name, notice: 'Name was successfully created.' }
          # format.json { render :show, status: :created, location: @name }
          format.html { redirect_to name, notice: 'Name was successfully created.' }
          format.json { render :show, status: :created, location: name }
        else
          name.errors.each { |attr, msg| @name.errors.add :name, msg } # attrから:nameに変更した
          format.html { render :new }
          format.json { render json: name.errors, status: :unprocessable_entity }
        end
      else
        format.html { render :new }
        # format.json { render json: @name.errors, status: :unprocessable_entity }
        format.json { render json: name.errors, status: :unprocessable_entity }
      end
    end
  end

 上記のプログラムでは、scaffoldで生成された部分をコメントアウトして残しておき、その直下に改造コードを挿入している。先ずフォーム専用のオブジェクトでバリデーション実行してから、本来のモデルでDBへの保存動作を行い、自動的にバリデーション動作をさせている。その際にエラーがあればフォーム専用のオブジェクトのエラーメッセージ用インスタンス変数にその内容をコピーして表示させている。なお、JSON出力に関してはテストを行っていない。

 更新処理も同様である:

# editからupdateに遷移
  def update
    name = Name.find(params[:id])
    @name = NameForm.new(name: params[:name_form][:name], persisted: true)
    family, first = @name.name.to_s.split(' ')
    respond_to do |format|
      # if @name.update(name_params)
      if @name.valid? and name.update_attributes(family_name: family, first_name: first) then
        # format.html { redirect_to @name, notice: 'Name was successfully updated.' }
        # format.json { render :show, status: :created, location: @name }
        format.html { redirect_to name, notice: 'Name was successfully updated.' }
        format.json { render :show, status: :ok, location: name }
      else
        name.errors.each { |attr, msg| @name.errors.add :name, msg } # attrから:nameに変更した
        format.html { render :edit }
        # format.json { render json: @name.errors, status: :unprocessable_entity }
        format.json { render json: name.errors, status: :unprocessable_entity }
      end
    end
  end

更新処理の場合は、新規作成より一段処理が少なくて済む。

i18n

 internationalizationの略だが、ここでは日本語化を指す。ActiveRecord派生のモデルにはja.ymlにactiverecordとしてモデル名や属性名の日本語化したものを記述しておけばよいが、ActiveModel::Modelをインクルードしただけのクラスについては、幸いなことに、ActiveModelとして扱えるので、次のように書いておけば日本語化される:

# ja.yml
ja:
  activerecord:
    models:
      name: "お名前"
    attributes:
      name:
        family_name: "家族名"
        first_name: "名前"
  activemodel:
    models:
      name_form: "お名前フォーム"
    attributes:
      name_form:
        name: "フルネーム"

尚、config/application.rbのApplicationクラス内に次の一文でデフォルトのロケールを指定しておくこと:

module FormEx1
  class Application < Rails::Application
    config.i18n.default_locale = :ja # これ
  end
end

ビューのちょっとした変更

 scaffoldなどで作っていた次のようなリンクがある場合、@nameにフォーム専用オブジェクトが代入されていれば当然エラーとなる:

<%= link_to 'Show', @name %>

これは次のように書くことで対処できる:

<%#= link_to 'Show', @name %>
<%= link_to 'Show', name_path(params[:id]) %> 

但し、scaffoldなどで生成したコントローラーのよにshowメソッドでparams[:id]から意図するオブジェクトが得られる場合に限る。例によって前に書いておいたものは残している。

その他

 ソースを載せようと思ったが、もうちょっと他に色々試してみてからgithubに置いておくことにする。