【ruby】ActiveModelを使ってDBと関係ないFormを作成する【Rails】
RailsでFormを扱う時は、Modelと紐付けることで、validationなどの設定はModelに書くことができ、非常に便利です。
ですが、DBと関係ない場合は紐付ける対象のModelが生成されていません。
そこで使用するのがActiveModelです。
上のページに書いてあることが全てなのですが、あまりにもショックが大きかったので書き残しておきます。
form_forは入力フォームに初期値を設定してくれる
Webアプリケーションで入力フォームを作成する場合、入力された情報を保持しておいて、次の画面の入力フォームに初期値として表示したいことがあります。検索画面で検索結果と一緒にフォームも表示する場合や、登録画面で入力エラーがあって同じ画面に戻ってきた時などです。
テキストボックスならvalue属性に入力された値をセットしてあげるだけでよいのですが、ラジオボタンや複数選択のチェックボックスなんかだと、選択肢をループで回して入力された値がvalue属性と一致したらchecked="checked"
を差し込んで…みたいなことをやる必要がありました。
Railsではform_for
ヘルパーがその辺の面倒な処理を面倒見てくれるのですが、呼び出すためにはモデルオブジェクトを渡す必要があります。
データを登録したり編集したりする画面なら、対応するモデルオブジェクトを渡せばいいのですが、検索フォームの入力項目というのはデータベースのテーブルとは完全に一致しない場合が多いと思います。
日付の範囲指定のために開始と終了で2つの項目が必要だったり、関連する別テーブルの値を条件にしないといけなかったりで、モデルクラスと似ているけど少しだけ違う形になります。
検索条件フォームはform_tagで書くしかないと思っていた
検索条件を入力するフォームには、対応するモデルクラスがないのでform_for
の恩恵には預かれません。モデルがなくても呼べるform_tag
というヘルパーメソッドを使うしかないと思って作成したフォームがこちらです。
画面イメージ
コントロール
class EmployeesController < ApplicationController
def index
@name = params[:name]
@sex_id = params[:sex_id]
@birth_from = params[:birth_from]
@birth_to = params[:birth_to]
@department_id = params[:department_id]
@favorite_ids = params[:favorite_ids]
@employees = # 検索処理は未実装...
end
def find
end
end
ビュー
_employees_find_form.html.erb
<%= form_tag({action: :index}, {method: :get}) do %>
<%# テキストボックス %>
<p>
<%= label_tag :name, '氏名:' %>
<%= text_field_tag :name, @name %>
</p>
<%# ラジオボタン選択 %>
<p>
<%= label_tag :sex_id, '性別:' %>
<%= radio_button_tag :sex_id, '', @sex_id == '' %>
<%= label_tag :sex_id_, '指定なし' %>
<% Sex.all.each do |option| %>
<%= radio_button_tag :sex_id, option.id, option.id.to_s == @sex_id %>
<%= label_tag "sex_id_#{option.id}", option.name %>
<% end %>
</p>
<%# テキストボックス(日付の範囲指定) %>
<p>
<%= label_tag :birth_from, '生年月日:' %>
<%= text_field_tag :birth_from, @birth_from %> 〜
<%= text_field_tag :birth_to, @birth_to %>
</p>
<%# プルダウン選択 %>
<p>
<%= label_tag :department_id, '部署:' %>
<%= select_tag :department_id,
options_from_collection_for_select(Department.all, "id", "name",
selected: @department_id),
include_blank: true %>
</p>
<%# チェックボックス複数選択 %>
<p>
<%= label_tag :favorite, '好物:' %>
<% Favorite.all.each do |option| %>
<%= check_box_tag "favorite_ids[]", option.id,
@favorite_ids.present? &&
@favorite_ids.include?(option.id.to_s),
id:"favorite_id_#{option.id}" %>
<%= label_tag "favorite_id_#{option.id}", option.name %>
<% end %>
</p>
<p>
<%= submit_tag '検索する' %>
</p>
<% end %>
すごくグチャグチャしています。メンテナンスするのも大変そうです。
ActiveModelを使えば対応するテーブルがなくてもform_forが使える
詳細は上のリンクにかいてありますが、ActiveModel
をinclude
したモデルクラスを検索フォームとして作ることで、form_for
に渡せるようになります。
というわけで書き直したものがこちらです。
モデル
class EmployeeFindForm
include ActiveModel::Model
attr_accessor :name, :sex_id, :birth_from, :birth_to,
:department_id, :favorite_ids
end
コントローラ
class EmployeesController < ApplicationController
def index
@form = EmployeeFindForm.new(params.require(:employee_find_form))
@employees = # 検索処理は未実装...
end
def find
@form = EmployeeFindForm.new
end
end
ビュー
<%= form_for @form, url: '/employees', method: :get do |f| %>
<%# テキストボックス %>
<p>
<%= f.label :name, '氏名:' %>
<%= f.text_field :name %>
</p>
<%# ラジオボタン選択 %>
<p>
<%= f.label :sex_id, '性別:' %>
<%= f.radio_button :sex_id, '', checked: @sex_id == '' %>
<%= f.label :sex_id_, '指定なし' %>
<%= f.collection_radio_buttons :sex_id, Sex.all, :id, :name %>
</p>
<%# テキストボックス(日付の範囲指定) %>
<p>
<%= f.label :birth_from, '生年月日:' %>
<%= f.text_field :birth_from %> 〜
<%= f.text_field :birth_to %>
</p>
<%# プルダウン選択 %>
<p>
<%= f.label :department_id, '部署:' %>
<%= f.collection_select :department_id, Department.all, :id, :name, include_blank: true %>
</p>
<%# チェックボックス複数選択 %>
<p>
<%= f.label :favorite, '好物:' %>
<%= f.collection_check_boxes :favorite_ids, Favorite.all, :id, :name, checked: @form.favorite_ids %>
</p>
<p>
<%= f.submit '検索する'%>
</p>
<% end %>
すごくスッキリしました!
「xxx_tag
なんて二度と使うもんか!」という気持ちです。