ガラシのパルプンテ頼み

地方産限界エンジニアのグローバル独り言

【5分くらいでわかる】form_withの使い方とfrom_for、form_tagとの違いについて | Rails入門

form_withとは

form_withとは、railsで情報を送信するためのヘルパーメソッド。 form_withを使うことにより、簡単に入力フォームに必要なHTMLを作成することができます。

api.rubyonrails.org

特定のコントローラーで任意のデータを受け取りたい or 受け取ったデータを永続化しない

  • 特定の画面のフォームから送られてきた値を任意のコントローラーで利用したい場合
  • または関連するモデルが存在しない = 送られてきた値を特定のモデルに永続化しない場合

これらの場合は、以下のようにurlオプションからコントローラーを指定します。 必要に応じてmethodオプションからHTTPメソッド(=アクション)を指定することも可能です。

<%= form_with url: users_path, method: :get do |form| %>
  <!--フォーム内容 -->
<% end %>

生成されるHTML

<form action=”/users” method=”get”>
・・・
</form>

送られてきたデータをDBに保存して永続化したい

  • 関連するモデルが存在する = フォームから送られてきたparamsをDBに保存したい場合

この場合は、保存したい対象のモデルをmodelオプションに指定します。 methodオプションは指定がなかった場合POSTがデフォルトのHTTPメソッドとして選択されます。

<%= form_with model: User.new do |form| %>
  <%= form.text_field :name %>
  <%= form.number_field :age %>
  <%= form.submit %>
<% end %>

生成されるHTML

<form action=”/users” method=”post”>
    <input type="text" name="user[name]" id="user[name]">
    <input type="number" name="user[age]" id="user[age]">
    <input type="submit" name="commit" value="保存する" data-disable-with="保存する"> 
</form>

渡ってくるparams

params
=> <ActionController::Parameters {
  "utf8"=>"✓", 
  "authenticity_token"=>"<token>", 
  "user"=>{"name"=>"garasi", "age"=>"29"}, 
  "commit"=>"送信", 
  "controller"=>"users", 
  "action"=>"create"
} permitted: false>

modelオプションが指定されている場合のデフォルトの挙動について

  • 該当のモデルオブジェクトから特定したcontroller・アクションに対するPOSTリクエストの送信
  • 渡されたインスタンスが空ならPOST(create), IDを持っていればPATCH(update)がHTTPメソッドとして指定される(methodオプションで任意のHTTPメソッドを指定することも可能)
  • ActiveRecordと連携してモデル名とモデルの属性名を、htmlのinput要素にあるname属性として付与する

ネストしたモデルに対してデータを永続化したい

子要素のモデルに対してデータを永続化する場合の記述の仕方が若干変わります。 ある記事に対してユーザーがコメントを投稿するフォームを例にあげて考えてみましょう。 まずはコントローラーにてインタンス変数の定義を行います。

def new
  @article = Article.find(params[:article_id])
  @comment = Comment.new
end

def edit
  @article = Article.find(params[:article_id])
  @comment = Comment.find(params[:id])
end

コメントは必ずいずれかの記事に紐づいているので、どの記事に対するコメントなのかという情報が必要となります。 そのため@article = Article.find(params[:article_id])でコメントが行われた記事を特定します。 ビュー側では下記のようなイメージで、引数に配列を渡す渡すことでネストされた子要素に対するリクエストの送信が可能となります。

<%= form_with model: [@article, @comment] do |form| %>
  <%= form.text_field :text %>
  <%= form.submit %>
<% end %>

Rails 5.1以前のformヘルパー

form_tag

  • 関連するモデルが存在しない = 送られてきた値を特定のモデルに永続化しない場合

form_withでいうところのurlオプションを使用する際の要件に一致する場合、5.1以前ではform_tagを用いてこれを実現していました。 使い方としては第一引数に送信先のURL、第二引数にリクエスト時のHTTPメソッドを指定する形です。

<%= form_tag('/main', method: :post) do %> 
  <input type="text" name="nickname"> 
  <input type="submit"> 
<% end %>

api.rubyonrails.org

form_for

  • 関連するモデルが存在する = フォームから送られてきたparamsをDBに保存したい場合

form_withでいうところのmodelオプションを使用する際の要件に一致する場合、5.1以前ではform_forを用いてこれを実現していました。 使い方としては引数としてモデルのインスタンスを渡すのみ。form_forではFormBuilderオブジェクトのヘルパーメソッドを使用できるため、フィールドの属性とインスタンスのプロパティを適宜指定します。

<%= form_for(@user) do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

api.rubyonrails.org

まとめ

form_withについて

  • 関連するモデルが存在しない = 送られてきた値を特定のモデルに永続化しない場合は、urlオプションでコントローラーとアクションを直指定してリクエストを飛ばす

  • 関連するモデルが存在する = フォームから送られてきたparamsをDBに保存したい場合は、modelオプションに任意のモデルのインスタンスを渡して、ORマッピングに沿ったデータの永続化を行う

  • ネストした子要素のモデルに対してはmodelオプションに配列を渡すことでリクエストできる

form_withと旧form_for, form_tagとの関係

  • modelとurlの指定により用途の使い分けを可能にし、書き方を統一したのがform_with

  • Rails5.1以降のアプリケーションでは基本的にform_withで書くのが一般的

  • 昔のプロダクトでform_tag, form_forを利用している箇所があった場合に、出力されるhtmlがイメージできる程度にはなっておく必要がある