[Rails]フォームヘルパー
はじめに
フォームヘルパーform_with
についてまとめてみました。
Railsのフォームヘルパーメソッド
Railsでは、form_with
、form_tag
、form_for
などのフォームヘルパーメソッドを提供しています。
Rails 5.1でform_with
が導入されるまでは、form_with
の機能はform_tag
とform_for
に分かれていました。
form_for
: form_for
メソッドは、モデルを基にしたフォームを作成するためのメソッドです。
<%= form_for @article do |form| %>
<%= form.label :title %>
<%= form.text_field :title %>
<%= form.label :content %>
<%= form.text_area :content %>
<%= form.submit "Create Article" %>
<% end %>
form_tag
: form_tag
メソッドは、モデルに依存しない独自のフォームを作成するためのメソッドです。
<%= form_tag articles_path do %>
<%= label_tag :title %>
<%= text_field_tag :title %>
<%= label_tag :content %>
<%= text_area_tag :content %>
<%= submit_tag "Create Article" %>
<% end %>
form_with
: form_with
メソッドは、Rails 5.1以降で導入された新しいフォームヘルパーメソッドです。それ以前のバージョンでは使用することができません。
# app/views/articles/new.html.erb
<%= form_with model: @article, url: articles_path do |form| %>
<div>
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :content %>
<%= form.text_area :content %>
</div>
<%= form.submit "Create Article" %>
<% end %>
form_with
-
デフォルトでAjaxリクエストをサポートする:
form_with
メソッドは、デフォルトでAjaxリクエストをサポートします。これにより、フォームの送信時にページ全体の再読み込みを回避し、非同期でフォームの処理結果を取得することができます。一方、他のフォームヘルパーメソッドでは、明示的にAjaxリクエストを指定する必要があります。 -
デフォルトの挙動:
form_with
メソッドは、フォームがモデルオブジェクトに基づいている場合、自動的に適切なHTTPメソッドとURLを設定します。また、フォームフィールドの生成やバリデーションエラーメッセージの表示など、様々な便利な機能を提供します。 -
シンプルな記法:
form_with
メソッドは、シンプルな記法を持っています。フォームのオプションをハッシュ形式で指定することができます。これにより、直感的で読みやすいコードを書くことができます。
フォームをオブジェクトに結び付ける
form_withの:model引数を使うと、フォームビルダーオブジェクトをモデルオブジェクトに紐付けできるようになります。
<%= form_with model: @article do |form| %>
<%= form.text_field :title %>
<%= form.text_area :body, size: "100x5" %>
<%= form.submit %>
<% end %>
<form enctype="multipart/form-data" action="/articles" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="B9xiJsqaZFqpbYIF3Ze8k8xeVkDSoyckXA-yylEkKw2xUTyC7qXtqweSRSdAeE04vs83Ra-2cTBPRCzETBiIMg" autocomplete="off">
<div>
<label for="article_title">タイトル</label>
<input type="text" name="article[title]" id="article_title">
</div>
<div>
<label for="article_body">本文</label>
<textarea name="article[body]" id="article_body" cols="100" rows="5"></textarea>
</div>
<input type="submit" name="commit" value="登録する" data-disable-with="登録する">
</form>
- フォームのactionには、@articleに適した値が自動入力されている。
- フォームのフィールドには、@articleにある値が自動入力されている。
- フォームのフィールド名は、article[...]という形でスコープされている。
- params[:article]がすべてのフィールドの値を含むハッシュになる
- コントローラ内でparams[:article][:title]でアクセスすると、送信された値を取り出せる
リソースを利用する
レコードをリソースとして宣言するとform_with
の呼び出しがはるかに簡単になります。
resources :articles
この短いform_with
呼び出しは、レコードの作成・編集のどちらでもまったく同じです。
## 新しい記事の作成
# 長いバージョン
form_with model: @article, url: articles_path)
form_with model: Post.new
# 短いバージョン(レコード識別を利用)
form_with model: @article
## 既存の記事の編集
# 長いバージョン
form_with model: @article, url: article_path(@article), method: "patch"
# 短いバージョン(レコード識別を利用)
form_with model: @article
# urlを渡す
<%= form_with url: posts_path do |form| %>
<%= form.text_field :title %>
<% end %>
# 生成されたhtml
<form action="/posts" method="post" data-remote="true">
<input type="text" name="title">
</form>
# スコープを指定する
<%= form_with scope: :post, url: posts_path do |form| %>
<%= form.text_field :title %>
<% end %>
# 生成されたhtml。名前がpostスコープに入っている
<form action="/posts" method="post" data-remote="true">
<input type="text" name="post[title]">
</form>
# Post.newという新しいPostオブジェクトを作成してフォームに関連付けする
<%= form_with model: Post.new do |form| %>
<%= form.text_field :title %>
<% end %>
# 生成されたhtml
<form action="/posts" method="post" data-remote="true">
<input type="text" name="post[title]">
</form>
# Post.firstで最初のpostを取得し更新する
<%= form_with model: Post.first do |form| %>
<%= form.text_field :title %>
<% end %>
# 生成されたhtml
<form action="/posts/1" method="post" data-remote="true">
<input type="hidden" name="_method" value="patch">
<input type="text" name="post[title]" value="<the title of the post>">
</form>
# モデルに含まれてない属性も追加できる
<%= form_with model: Cat.new do |form| %>
<%= form.text_field :cats_dont_have_gills %>
<%= form.text_field :but_in_forms_they_can %>
<% end %>
# 生成されたhtml
<form action="/cats" method="post" data-remote="true">
<input type="text" name="cat[cats_dont_have_gills]">
<input type="text" name="cat[but_in_forms_they_can]">
</form>
よく使うパラメーター
-
model
: フォームのモデルオブジェクトを指定します。例えば、model: @post
とすると、@post
インスタンスに関連するフォームが作成されます。 -
url
: フォームデータの送信先URLを指定します。例えば、url: posts_path
とすると、posts_path
にデータが送信されます。指定しない場合はフォームを現在のページに送ります。 -
method
: フォームの送信メソッドを指定します。デフォルトは自動的に適切なHTTPメソッドが選択されますが、明示的に指定することもできます。例えば、method: :patch
とすると、PATCHリクエストが使用されます。 -
scope
: フォームのスコープを指定します。主にネストされたリソースのフォームを作成する場合に使用します。 -
local
: フォームの送信時にAjaxを使用するかどうかを指定します。デフォルトはfalse
で、ページの再読み込みが行われます。remote: true
を指定すると、Ajaxリクエストが行われます。
コントローラー
コントローラ内でパラメータをモデルに渡す前に、定番のパラメータの許可リストチェックを宣言する必要があります。
def create
@person = Person.new(person_params)
# ...
end
private
def person_params
params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street])
end
ネストされたリソース
ネストされたリソースのフォームを作成するためmodelパラメーターにネストされたリソースの関係を表現します。
Article
モデルと Comment
モデルがあるとします。
Article
は複数のコメントを持つ関連付けがあります。
resources :articles do
resources :comments
end
上記のルーティング設定により、articles/:article_id/comments
のURLでコメントを作成することができます。
<h1><%= @article.title %></h1>
<!-- Articleの情報を表示 -->
# コメント一覧
<h2>コメント</h2>
<%= render @article.comments %>
# コメントフォーム
<%= form_with(model: [@article, @comment]) do |form| %>
<%= form.text_area :body %>
<%= form.submit %>
<% end %>
form_with
の model
パラメーターには [@article, @comment]
を指定しています。これにより、ネストされたリソースのフォームが作成されます。コメント作成フォームの送信先は articles/:article_id/comments
となります。
ネストされたフォーム
ネストされたフォームを使うと、親モデルと関連する子モデルのデータを同時に送信できます。親モデルと子モデルの関連性を設定し、fields_for
メソッドを使用して子モデルのフォームを作成します。
例えば、ユーザー(User)とその住所(Address)を同時に作成/更新する場合などです。ユーザーと住所のフォームを同じページに表示し、一度に入力して送信することで、関連するモデルをまとめて作成/更新します。
# app/models/post.rb
class User < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses
end
# app/models/address.rb
class Address < ApplicationRecord
belongs_to :user
end
# app/views/users/_form.html.erb
<%= form_with model: @person do |form| %>
Addresses:
<ul>
<%= form.fields_for :addresses do |addresses_form| %>
<li>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
<%= addresses_form.label :street %>
<%= addresses_form.text_field :street %>
...
</li>
<% end %>
</ul>
<% end %>
accepts_nested_attributes_forヘルパーが受け取るのはこのようなパラメータの名前です。たとえば、2つの住所を持つユーザーを1人作成する場合、送信されるパラメータは以下のようになります。
{
'person' => {
'name' => 'John Doe',
'addresses_attributes' => {
'0' => {
'kind' => 'Home',
'street' => '221b Baker Street'
},
'1' => {
'kind' => 'Office',
'street' => '31 Spooner Street'
}
}
}
}
検索フォーム
検索フォームを作成するためにこちらのものが必要です。
- GETメソッドを送信するためのフォーム要素
- 入力するものを示すラベル
- テキスト入力要素
- 送信ボタン要素
form_with
とその中で生成されるフォームビルダーオブジェクトを使った検索フォームになります。
<%= form_with url: "/search", method: :get do |form| %>
<%= form.label :query, "キーワード" %>
<%= form.text_field :query %>
<%= form.submit "検索する" %>
<% end %>
出力は以下のようになります。
<form action="/search" method="get" accept-charset="UTF-8" >
<label for="query">Search for:</label>
<input id="query" name="query" type="text" />
<input name="commit" type="submit" value="Search" data-disable-with="Search" />
</form>
フォームコントロール
フォームコントロールはユーザーからの入力を受け付けたり、操作したりすることのできる要素です。
チェックボックス
チェックボックスはフォームコントロールの一種で、ユーザーがオプションをオンまたはオフにできるようにします。
<%= form.check_box :pet_dog %>
<%= form.label :pet_dog, "犬" %>
<%= form.check_box :pet_cat %>
<%= form.label :pet_cat, "猫" %>
出力は以下のようになります。
<input type="checkbox" id="pet_dog" name="pet_dog" value="1" />
<label for="pet_dog">犬</label>
<input type="checkbox" id="pet_cat" name="pet_cat" value="1" />
<label for="pet_cat">猫</label>
check_box
の第1パラメータはinput
の名前です。第2パラメータはinput
要素のvalue
属性を指定します。チェックボックスをオンにすると、この値がフォームデータに含まれ、最終的にparams
に渡されます。
ラジオボタン
チェックボックスと同様、ラジオボタンも一連のオプションをユーザーが選択できますが、一度に1つの項目しか選択できない排他的な動作が特徴です。
<%= form.radio_button :age, "child" %>
<%= form.label :age_child, "21歳以下" %>
<%= form.radio_button :age, "adult" %>
<%= form.label :age_adult, "22歳以上" %>
出力は以下のようになります。
<input type="radio" id="age_child" name="age" value="child" />
<label for="age_child">21歳以下</label>
<input type="radio" id="age_adult" name="age" value="adult" />
<label for="age_adult">22歳以上</label>
check_box
ヘルパーと同様、radio_button
の第2パラメータはinput
要素のvalue
属性を指定します。2つのラジオボタン項目は同じ名前('age')を共有しているので、ユーザーは一方の値だけを選択できます。そしてparams[:age]
の値は"child"と"adult"のいずれかになります。
チェックボックスとラジオボタンには必ずラベルを表示してください。ラベルを表示することで、そのオプションとラベルの名前が関連付けられるだけでなく、ラベルの部分もクリック可能になるのでUIが向上します。
その他のヘルパー
他にも、以下の「テキストエリア」「隠しフィールド」「パスワードフィールド」「数値フィールド」「日付時刻フィールド」など多くのフォームコントロールがあります。
<%= form.text_area :message, size: "70x5" %>
<%= form.hidden_field :parent_id, value: "foo" %>
<%= form.password_field :password %>
<%= form.number_field :price, in: 1.0..20.0, step: 0.5 %>
<%= form.range_field :discount, in: 1..100 %>
<%= form.date_field :born_on %>
<%= form.time_field :started_at %>
<%= form.datetime_local_field :graduation_day %>
<%= form.month_field :birthday_month %>
<%= form.week_field :birthday_week %>
<%= form.search_field :name %>
<%= form.email_field :address %>
<%= form.telephone_field :phone %>
<%= form.url_field :homepage %>
<%= form.color_field :favorite_color %>
出力は以下のようになります。
<textarea name="message" id="message" cols="70" rows="5"></textarea>
<input type="hidden" name="parent_id" id="parent_id" value="foo" />
<input type="password" name="password" id="password" />
<input type="number" name="price" id="price" step="0.5" min="1.0" max="20.0" />
<input type="range" name="discount" id="discount" min="1" max="100" />
<input type="date" name="born_on" id="born_on" />
<input type="time" name="started_at" id="started_at" />
<input type="datetime-local" name="graduation_day" id="graduation_day" />
<input type="month" name="birthday_month" id="birthday_month" />
<input type="week" name="birthday_week" id="birthday_week" />
<input type="search" name="name" id="name" />
<input type="email" name="address" id="address" />
<input type="tel" name="phone" id="phone" />
<input type="url" name="homepage" id="homepage" />
<input type="color" name="favorite_color" id="favorite_color" value="#000000" />
隠しinput
はユーザーには表示されず、種類を問わず事前に与えられた値を保持します。隠しフィールドに含まれている値はJavaScript
で変更できます。
終わりに
form_with
を理解する上で難しいと感じることは、パラメーターの使い方とオプションの意味を正確に把握することです。form_with
は多くの異なるオプションを提供していますが、それぞれのオプションがどのように機能し、どのようにフォームの動作に影響を与えるのかを理解することは重要です。
Discussion