form_with を使ったテキストとセレクトボックスの検索フォーム


筆者はRails6.0ですが、Rails5.1以降であれば動くはずです。またSearchkick(Elasticsearchを簡単にするGem)を使っているので、もしかすると環境依存があるかもしれません。

Rails5.1以降からフォームの生成にはform_withを使った記法が推奨されています。しかしこと検索フォームの作成においては、未だに情報が少なく苦戦したのでメモ。

基本的には以下の記事がform_withの使い方を分かりやすくまとめてくれている👇
https://pikawaka.com/rails/form_with

form_with は form_tag と form_for を統一したもの

form_tag(非推奨)

  • 通常のフォーム
<%= form_tag users_path do %>
  <%= text_field_tag :email %>
  <%= submit_tag %>
<% end %>

form_for(非推奨)

  • モデルに基づいたフォームを作成したい時に使う
  • フォームに入力されたデータを保存する必要がある時に使う
  • フォームビルダー(よく|f|で表現されるやつ)が必要
<%= form_for @user do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

form_with

  • フォームビルダーが必要

関連するモデルがない form_with :

<%= form_with url: users_path do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

関連するモデルがある form_with :

<%= form_with model: @user do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

【Rails 5】(新) form_with と (旧) form_tag, form_for の違い

Searchkickでの利用を想定したフォーム

index.html.erb
<%= form_with model: @post, method: "get", local: true do %>
    <%= text_field_tag :q, params[:q], class: "form-control ds-input", placeholder: "検索‥" %>
<% end %>
post_controller.rb
def index
    @posts = self.query
end

private
    def query
      if params[:q]
        Post.search params[:q], fields: [:name], operator: "or"
      else
        Post.all
      end
    end

local: trueというのは form_with のデフォルト設定でAjaxによるHTTPリクエストを送ってしまうのをやめさせているようです(Ajaxだと画面遷移してくれないらしい)。

検索フォームにおけるメソッドはGETらしく、POSTだと検索結果をブックマークして再アクセスした時(リロードした時も?)に結果が上手く表示されないらしい(そもそもPOSTでも動くのか?)

ラベルと collection_select によるセレクトボックスを追加

この検索フォームDBのAreasテーブルの値を元にしたセレクトボックス(ドロップダウンリスト、プルダウンリストなどとも呼ばれるがW3C的にはセレクトボックスが正式らしい)を付けたかった。

<%= form_with model: @post, method: "get", local: true do |f| %>
    <%= f.text_field :q, {class: "form-control ds-input", placeholder: "検索‥"} %>
    <%= f.label :area, "エリア" %>
    <%= f.collection_select :area, @areas, :id, :name, {prompt: ""} %>
<% end %>

上記コードで以下HTMLが生成される。

<form action="/" accept-charset="UTF-8" method="get">
    <input class="form-control ds-input" placeholder="検索‥" type="text" name="q" id="q">
    <label for="area">エリア</label>
    <select name="area" id="area"><option value=""></option>
        <option value="1">北海道</option>
        <option value="2">青森県</option>
    </select>
    <input type="submit" name="commit" value="検索" data-disable-with="検索">
</form>

collection_selectの使い方に関しては以下の記事が分かりやすい👇
https://qiita.com/_akira19/items/c218186983f444c2d794

ややこしい点

form_with周りでややこしいのは以下のようにしてもフォームが生成されてしまう点。

# これは正しくないコード
<%= form_with model: @post, method: "get", local: true do %>
    <%= text_field_tag :q, params[:q], class: "form-control ds-input", placeholder: "検索‥" %>
    <%= collection_select @post, :area, @areas, :id, :name, {prompt: ""} %>
<% end %>

form_withなのにフォームビルダー|f|を使っていないし、text_fieldではなくtext_field_tagを使っている。またcollection_selectの第一引数にモデルを渡す形になっている(渡さないとエラーになる)。

Rubyの前提知識

筆者はProgateあがりですが、RubyやRailsの公式チュートリアルをやっていないせいかformまわりの基礎知識がバラバラです…。もしかすると当たり前かもしれませんが、知らなかった知識を書いときます。

オブジェクト

Rubyで扱われるデータ全体?

ハッシュとは

key:valueの関係。Swiftで言う辞書。

スコープとは

引数も含めたメソッドのエイリアス(代替)っぽいが、検索フォームにおいてどう言うスコープが定義され、またフォームに使われる(渡される)のかイメージできず。

ブロックとは

メソッド呼び出しの際に引数と一緒に渡すことのできる処理のかたまり | たのしいRuby P199

フォームビルダーとは

Rails でフォームを作成するときに form_for メソッドを使用しますが、form_for に渡すブロックの引数に渡されるオブジェクトがフォームビルダーです。言葉では分かりづらいですが、以下のコードにおけるformがそれです。

<%= form_for(@user) do |form| %>
  # ブロック引数の form がフォームビルダーです.
<% end %>

フォームビルダーの実体は ActionView::Helpers::FormBuilder クラスであり、ブロック引数に渡されるのは ActionView::Helpers::FormBuilder のインスタンスです。

普段「<%= form.text_field(:name) %>」のように便利に使っているメソッドは ActionView::Helpers::FormBuilder で定義されています。

引用元

メソッドの括弧は省略可能

ヘルパーもメソッドらしく括弧は省略可能。

# 以下は同じ意味
<%= text_field(:search, params[:search]) %>
<%= text_field :search, params[:search] %>

公式ドキュメント『Railsガイド』のフォームの説明

本家RailsGuidesの日本語役であるRailsガイドのフォームヘルパーの説明。正直分かりにくい。と言うかform_tagを使って説明されているので「え、form_withじゃないの?」となって余計わからんくなる。

一般的な検索フォーム

form_tagの第一引数はアクションへのパスで、第二引数はオプションのハッシュ(method: "get"class: "nifty_form"などのことらしい)。

<%= form_tag("/search", method: "get") do %>
  <%= label_tag(:q, "Search for:") %>
  <%= text_field_tag(:q) %>
  <%= submit_tag("Search") %>
<% end %>

上のコードから以下のHTMLが生成されます。

<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
  <label for="q">Search for:</label>
  <input id="q" name="q" type="text" />
  <input name="commit" type="submit" value="Search" />
</form>

ただしハッシュを2つ以上使いたい場合は{}で囲んでやる必要があるらしい。

form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_form")

上記のコードから以下が生成される👇

<form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">

参考