Rails入門 | form_withの使い方とfrom_for、form_tagとの違いについて

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

### 参考
https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with

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

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

### Rails(view)
```ruby
<%= form_with url: users_path, method: :get do |form| %>
  <!--フォーム内容 -->
<% end %>
```

### 復元されるhtml

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


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

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

### Rails(view)
```ruby
<%= form_with model: User.new do |form| %>
  <%= form.text_field :name %>
  <%= form.number_field :age %>
  <%= form.submit %>
<% end %>
```

### 復元されるhtml
```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
```ruby
params
=> <ActionController::Parameters {
  "utf8"=>"✓", 
  "authenticity_token"=>"<token>", 
  "user"=>{"name"=>"garasi", "age"=>"29"}, 
  "commit"=>"送信", 
  "controller"=>"users", 
  "action"=>"create"
} permitted: false>
```


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


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

```ruby
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メソッドを指定する形です。

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

### 参考
https://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag


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

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

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

### 参考
https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for

## まとめ
### 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がイメージできる程度にはなっておく必要がある