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に置いておくことにする。