検索条件フォームのようにテーブルと完全に同一でないフォームもform_forを使って実装できる

  • 81
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

【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というヘルパーメソッドを使うしかないと思って作成したフォームがこちらです。

画面イメージ

Practice 2015-02-02 23-16-47.png

コントロール

app/controllers/employees_controller.rb
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

ビュー

app/view/employees/
_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が使える

詳細は上のリンクにかいてありますが、ActiveModelincludeしたモデルクラスを検索フォームとして作ることで、form_forに渡せるようになります。
というわけで書き直したものがこちらです。

モデル

app/models/employee_find_form.rb
class EmployeeFindForm
  include ActiveModel::Model

  attr_accessor :name, :sex_id, :birth_from, :birth_to,
    :department_id, :favorite_ids
end

コントローラ

app/controllers/employees_controller.rb
class EmployeesController < ApplicationController
  def index
    @form = EmployeeFindForm.new(params.require(:employee_find_form))
    @employees = # 検索処理は未実装...
  end

  def find
    @form = EmployeeFindForm.new
  end
end

ビュー

app/view/employees/_employees_find_form.html.erb
<%= 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なんて二度と使うもんか!」という気持ちです。