railsのform_withとfiles_forの使い方は完璧でしょうか?モデルのオプション指定方法などが難しかったりします。
form_withとfiles_forの使い方があまり検索に出てこないので、深掘りしていこうと思います。modelオプションをすることのメリットも説明しつつ使い方を解説します。
Contents
form_withの書き方
まずは、よくある書き方です。復習しておきましょう。
# erb
<%= form_with model: @comment, url: comment_path, html: {class: "comment_form"}, local: true do |f| %>
# haml
= form_with model: @comment, url: comment_path, html: {class: "comment_form"}, local: true do |f|
オプションには、modelやurlやhtmlやlocalがあります。
local: trueをつけると同期処理になります。忘れやすいです。
form_withのmodelオプション
modelオプションには大抵@commnentだったり、@postだったりを渡します。
コントローラーで定義していることが重要です。
def new
@comment = Comment.new
end
def edit
@post = Post.find(params[:id])
end
newアクションであれば、新規のインスタンス(Comment.newなど)
editアクションであれば、これから編集する一件のインスタンスが理想です。
こうしてあげることで、modelオプションが輝きます。
<%= form_with model: @comment %>
<%= form_with model: @post %>
newアクションのページでもeditアクションのページでも@xxxxとしていれば流用できるので、_form.html.xxxと部分テンプレートにするのが一般的です。
別々で書くならば以下のようにviewファイルで指定しちゃってもいいです。あまり見かけませんが可能です。
<%= form_with model: Comment.new %>
<%= form_with model: Post.find(params[:id]) %>
modelオプションをすることのメリット
メリットは以下の三点ぐらいがあげれます。
- urlを自動生成してくれる
- エラーメッセージの部分テンプレートにf.objectを渡すことができる
- f.text_areaなどのヘルパーメソッドが使用できる
<%= form_with model: @comment do |f| %>
<%# 1.@commentの中身がComment.newであれば、createアクションへのurl自動生成>
<%# @commentの中身がComment.find(params[:id])であれば、updateアクションへのurl自動生成>
<%= render 'shared/error_messages', model: f.object %>
<%# 2.f.objectに@commentの中身が入っているので、エラーテンプレートにインスタンスを渡すときに便利
<%= f.text_area :content %>
<%= f.text_field :nickname %>
<%# 3.f.text_areaなどのヘルパーメソッドを使用できる %>
<% end %>
deviseを使用しているときのform_withのmodel指定
deviseを使用していてdeviseのコントローラを生成していない場合は、コントローラに@userなどがない状況なので、form_withのmodel指定に困ります。
deviseでは、gemの中で、resourceという変数にuserのインスタンスをいれてくれているので、resourceを使用していけばいいです。urlの指定も忘れないようにしましょう。
<%= form_with model: resource, url: user_registration_path do |f| %>
fields_forの文法
話は代わり、fields_forというものがあります。ネストされたものをparamsで送りたいときに使用します。特にネストされたものを複数一気に保存する際に効果を発揮します。
例えば以下のようなものです。
- itemモデルに紐づいたpictureモデル
- userモデルに紐づいたaddressモデル
- postモデルに紐づいたtagモデル
使い方は以下のような感じです。
<%= form_with model: @item do |f| %>
<%= f.fields_for :pictures do |i| %>
<%= i.file_field :src %>
<% end %>
<% end %>
注目ポイントは、2行目の:picturesとしている部分です。ここは@itemのメソッド名がきます。アソシエーションでhas_manyで紐づけていると@item.picturesが使えると思うので、.picturesのところがf.fields_forの第一引数にくると認識してくれればOKです。
class Item < ActiveRecord::Base
has_many :pictures
end
railsのドキュメントにも紹介コードが乗っています。
https://railsdoc.com/page/fields_for
fields_forはbuildされている回数分ループする
以下の記述のコントローラ、newアクションだとfields_forの部分は一切表示されません。
# 誤った実装
## controllerファイル
def new
@item = Item.new
end
## viewファイル
<%= form_with model: @item do |f| %>
<%= f.fields_for :pictures do |i| %>
<%# この中身は0回表示、つまり表示されない %>
<%= i.file_field :src %>
<% end %>
<% end %>
fields_forの中身を正しく表示するには指定回数分ループしておく必要があります。
# 正しい実装
## controllerファイル
def new
@item = Item.new
# itemに紐付くpictureを一回buildしておく
@item.pictures.build
end
## viewファイル
<%= form_with model: @item do |f| %>
<%= f.fields_for :pictures do |i| %>
<%# この中身は1回表示(fileの選択ボタンが1個でる) %>
<%= i.file_field :src %>
<% end %>
<% end %>
もちろん5回ビルドすると5回ループしますし、editアクションでは既存で紐づいているpictureの回数分ループします。
# 正しい実装
## controllerファイル
def new
@item = Item.new
5.times do
@item.pictures.build
end
end
def edit
@item = Item.find(params[:id])
# itemには既存にpictureが5枚紐づいている時
end
## viewファイル
<%= form_with model: @item do |f| %>
<%= f.fields_for :pictures do |i| %>
<%# 上記new、editいずれも、この中身は5回表示(fileの選択ボタンが5個でる) %>
<%= i.file_field :src %>
<% end %>
<% end %>
fields_forを指定した時のparamsの形
上記fields_forを使用した場合のparamsは以下のようになります。
{
"utf8"=>"✓",
"authenticity_token"=>"xxxx",
"item"=>{
"pictures_attributes"=>{
"0"=>{
"src"=>#<ActionDispatch::Http::UploadedFile:0xhogehoge
@tempfile=#<Tempfile:hoge.png>,
@original_filename="hoge.png",
@content_type="image/png", ...>
},
"1"=>{
"src"=>#<ActionDispatch::Http::UploadedFile:0xhogehoge
@tempfile=#<Tempfile:hoge.png>,
@original_filename="hoge.png",
@content_type="image/png", ...>
}
......
},
},
"commit"=>"送信"
}
実際に使用した例としてこちらの記事も多く読まれています。
コメント