LoginSignup
124

More than 5 years have passed since last update.

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

Posted at

【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なんて二度と使うもんか!」という気持ちです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
124