form_withの基本
form_withは非常によく使うヘルパーですが、基本的な挙動がわかっていないとハマってしまうので、ぜひ理解したいところです。
一番基本的な書き方は以下の書き方でしょうか。(今回のUserモデルのカラムはnameとemailだけを想定)
<%= form_with model: @user do |f| %>
<!--フォーム内容 -->
<% end %>
以下のように直接書くこともできます。
<%= form_with model: User.new do |f| %>
<!--フォーム内容 -->
<% end %>
.
.
.
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to user_path(@user)
else
render :new
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
redirect_to request.referer
else
render :new
end
end
def destroy
@user = User.find(params[:id])
@user.destroy
redirect_to request.referer
end
private
def user_params
params.require(:user).permit(:name, :email)
.
.
.
form_withを先ほどのように書くと、railsではデフォルトとして、
Userモデルのnew=>createとよきに計らって勝手にレールを準備してくれる。
今回の場合、railsは
「ふむふむ、User.newということは、データが空だからcreateアクションに飛ばせばいいんだな」と判断してくれる。
ただし、modelが存在しない場合はurlとscopeを指定してあげる必要がある。(この後説明します。)
ここを適切にやらないと以下のエラーがにハマっちゃいます。
form_withの応用
応用といってもそんなに難しくありません。多分使っていたら、こっちのアクションに飛ばしたいなーとか思い始めると思います。そんなときには、form_withオプションの使用を検討しましょう。
オプション | 説明 | デフォルト値 |
---|---|---|
:model | モデルを指定 | |
:scope | スコープを指定 | |
:url | URLを指定 | |
:local | リモート送信の無効 | false |
:namespace | 名前空間を指定 | |
:method | HTTPメソッドを指定 | POST |
:id | id属性を指定 | |
:class | class属性を指定 |
この中でも特に重要なのが、以下の4点です。
- model(説明済み)
- url
- method
- scope
今回url/method/scopeの3つに絞って説明していきます。
localについては、ajaxの記事を書く時に説明する予定です。
また他はそのままの意味なので適宜使い方を調べてください。
url
urlとはpathのことだと思っても大丈夫です。
先ほど以下のような基本的な書き方を紹介しました。
<%= form_with model: @user do |f| %>
<!--フォーム内容 -->
<% end %>
しかし、これだとrailsが勝手にレールに沿ってパラメータを送るので融通が効きません。
「なんか思ったところに飛ばないな」とか「確認画面を作りたいな」とかいう時に使います。
urlはこんな感じで書きます。
<%= form_with url: users_path, method: :post do |f| %>
<!-- フォーム内容 -->
<% end %>
一言でまとめると、「controllerのこのアクションに飛ばしたい」というときに、そのパスとメソッドを指定しましょう。
method
これはそのままなのですが、httpはWeb上の情報を伝達するための決まり事です。
で、そのリクエストの方法にget/post/put/patch/deleteがあります。
つまり、どのようなことをしたいかによって、このメソッドを指定しないといけないわけです。
methodを簡単にまとめると、以下の通りです。
リクエスト(method) | 説明 |
---|---|
GET | 特定のリソースを取得する |
POST | サーバーに新しいリソースを作成する |
PUT | リソースの更新をサーバーに伝える PUTリクエストは、更新対象のリソースを完全に置き換える |
PATCH | PUTリクエストと同様に、リソースの更新をサーバーに伝える 更新対象のリソースを部分的に更新する |
DELETE | サーバーからリソースを削除する |
で、これを指定してやるわけです。
時々、POSTのパスに飛ばしているのに、GETに飛ぶこともあるので、明示的に指定しましょう。
scope
scopeについては、以下を比較すればわかりやすいです。
- scopeを使わずにurlでpathを指定した場合
- scopeを使ってurlでpathを指定した場合
*scopeを使わずにurlでpathを指定した場合
<%= form_with url: users_path, local: true do |f| %>
<%= f.label :name, "name" %>
<%= f.text_field :name %>
<%= f.submit "submit" %>
<% end %>
これから生成されるHTMLは次のようになります。(重要部分だけ抽出)
<form action="/users" accept-charset="UTF-8" method="post">
省略
<label for="name">name</label>
<input type="text" name="name" id="user">
省略
</form>
*scopeを使ってurlでpathを指定した場合
<%= form_with url: users_path, scope: :user, local: true do |f| %>
<%= f.label :user, "name" %>
<%= f.text_field :name %>
<%= f.submit "submit" %>
<% end %>
生成されるHTMLはこうなります。(重要部分だけ抽出)
<form action="/posts" accept-charset="UTF-8" method="post">
省略
<label for="user_name">name</label>
<input type="text" name="user[name]" id="user">
省略
</form>
labelのfor属性、inputのid属性がnameからuser_titleに変わり、name属性がnameからuser[name]に変わりました。
つまり、scopeを使うことで送られるパラメーターの構造が変わります。したがって、これを考慮しないとストロングパラメータで拒否されるわけですね。
・scopeを設定していない場合
params[:name]でnameを取得できる
・scope: userを設定した場合
params[:user][:name]でnameを取得できる
ちなみにモデルのインスタンスを与えた場合は、scopeを設定しなくても初めからグループ化されています。
つまり、インスタンスを与えない場合は、このscopeの指定の仕方に気をつけないといけないわけですね。
ルーティングでネストしている場合
ルーティングでネストを定義している時は書き方が少し変わります。
ただし、基本を押さえれば全然難しくありません。
ユーザーにコメントを投稿するフォームを例に挙げてみます。
まず、コントローラは以下のように書きます。
def new
@user = User.find(params[:user_id])
@comment = Comment.new
end
def edit
@user = User.find(params[:user_id])
@comment = Comment.find(params[:id])
end
で、ユーザーの個別ページには以下のようにform_withを書きます。
個別ページなのでurlは"/users/1"みたいになっています。
<%= form_with model: [@user, @comment] do |form| %>
<%= form.text_field :text %>
<%= form.submit %>
<% end %>
しかし、今回はform_withの書き方が少し違いますね。
model: [@user, @comment]
となっています。
これはどういうことか?コントローラを見ればわかります。
今回は"/users/1"のページにいるので、user_idが1番だとわかります。
つまり、@userはidが一番のユーザーに決まります。
で、それに対して、Comment.newで、コメントをユーザーのidが1番に対して作成するわけです。
つまり、「idが1番のユーザーに対して、新しいコメントを作成するよ」ということを
@userと@commentの2つで示しているわけです。
以下が生成されるhtmlです。
updateについても同様に考えれば良いので割愛します。ぜひ、editアクションはこのような書き方をするのか考えてみてください。
# create時の例
<form action="/users/1/comments" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓" />
# update時の例
<form action="/users/1/comments/1" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓" /><input type="hidden" name="_method" value="patch" />
メソッドの一覧
form_withで使えるメソッドはたくさんあります。一つひとつ使って挙動を確認してみるのがいいですね。
個人的には太字にした部分が頻繁に使ったり、重要なものだと思います。他も使ったりしますが、個人的な使用頻度は低めです。
メソッド | 説明 |
---|---|
form.text_field | 一行のテキスト投稿フォーム |
form.text_area | 複数行のテキスト投稿フォーム |
form.number_field | 数値入力ボックスを生成 |
form.search_field | 一行の検索フォーム |
form.email_field | メールアドレス入力ボックスを生成 |
form.check_box | データベースの情報を使わずにチェックボックスを生成 |
form.collection_check_boxes | データベースの情報を元にチェックボックスを生成 |
form.select | 選択肢を作成 |
form.collection_select | データベースの情報を元に選択肢を生成 |
form.file_field | ファイル選択ボックスを生成 |
form.datetime_field | 日時の入力欄を生成 |
form.date_select | 日付選択ボックスを生成 |
form.hidden_field | 非表示のフォーム |
form.submit | 送信ボタンの生成 |
まとめ
以上がform_withのまとめでした。
まとめると、
基本的には基本形を使用しましょう。
form_with model: model_instance
で、以下のような時には、scope/method/urlを駆使していきましょう。
- 「パラメータが思ったところに飛ばない」
- 「他のところに飛ばしたい」
補足ですが、思ったところに飛ばない原因がform_withの書き方以前に、
rails6の場合、rails_ujsの問題であったり、
rails7の場合、書き方が違ったりなど別問題の時もあるので、その場合は他の原因も探ってみましょう。
data: { "turbo-method": :post }