【Rails6】form_withとfields_forのmodel・インスタンス指定方法

  • このエントリーをはてなブックマークに追加

railsのform_withとfiles_forの使い方は完璧でしょうか?モデルのオプション指定方法などが難しかったりします。

form_withとfiles_forの使い方があまり検索に出てこないので、深掘りしていこうと思います。modelオプションをすることのメリットも説明しつつ使い方を解説します。

fields_forの書き方

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オプションをすることのメリット

メリットは以下の三点ぐらいがあげれます。

  1. urlを自動生成してくれる
  2. エラーメッセージの部分テンプレートにf.objectを渡すことができる
  3. 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"=>"送信"
}

実際に使用した例としてこちらの記事も多く読まれています。

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。

スポンサードリンク

コメント

コメントを残す

*