【Ruby on Rails】form_withヘルパーを押さえる

## 対象読者（学習前の自分）
- rails初学者
- form_withヘルパーを魔術に感じている方

## form_withヘルパーでできること
**フォームの自動生成。**
こんなコードを書くと...
``` _form.html.erb
<%= form_with(model: @user) do |form| %>

  <%= form.label :name %>
  <%= form.text_field :name, class: 'form-control' %>

  <%= form.label :email %>
  <%= form.email_field :email, class: 'form-control' %>

  <%= form.label :password %>
  <%= form.password_field :password, class: 'form-control' %>

  <%= form.label :password_confirmation, "Confirmation" %>
  <%= form.password_field :password_confirmation, class: 'form-control' %>

  <%= form.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
```

このようにフォームを生成してくれる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629824/1958c5e3-6705-acb1-7337-8267e2a6eaca.png)

編集ページならデフォルト値も入れてくれる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629824/de0cbd38-746f-64f9-4040-db791d9c2b5d.png)

**form_withヘルパーとしての主な機能はこれ＝裏でhtml記述を自動生成してくれている だけ。**


## form_withヘルパーの使い方
どの記述で何をしているのか。

**本体**
```
<%= form_with(model: @user) do |f| %>
```
- "model"オプション
    - フォームを表示する画面に渡ってきたインスタンスを受け取る。ここではuserインスタンス
    - 編集画面で登録済みユーザーの情報をデフォルト値として入れておける
        - ログイン後に訪れる編集画面（/users/:id/edit）ではuserインスタンスが渡ってくる
            - →渡ってきたインスタンスを使ってデフォルト値埋め
        - 新規登録画面（/users/new）では、インスタンスは渡ってこないので`@user`はnil

- do {form}
    - ブロック。この記述により、ブロック内で`form.xxx`の形で便利な各種メソッドを呼び出せる（form_withヘルパーの理解にあたっては、ブロックを必ずしもちゃんと理解している必要はない）


**label**
```
<%= f.label :name %>
```
- ここを生成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629824/f0b9e589-1505-193c-2290-1e6b761b0183.png)
- "Name"でなく"名前"にしたいなど、表示を変更したいときは
`<%= f.label :name, "名前" %>`
のようにカンマ+文字列で指定してあげれば変更できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629824/f7effe9e-0b79-bed3-b1fc-1c4797e0b25a.png)


**input定義**
```
<%= form.text_field :name, class: 'form-control' %>
```
- form.text_field
    - フォーム要素の種別を指定できる
        - text_field：1行のテキストボックス
        - email_field：メールアドレス
        - password_field：パスワード
        - etc...もちろんラジオボタンやチェックボックスなども設定可能
            - 参考：https://railsguides.jp/form_helpers.html
- :name
    - フォームの送信先となるcontrollerのcreate/updateメソッドの引数名↓、及びモデルのパラメータ名（DBのカラム名）と一致させる
    - これにより以下が実現できる
        - フォームの送信をcontrollerで受け取り、DBを更新する
        - DBのデータでフォーム要素をデフォルト値埋めする（編集画面のみ）
```users_controller.rb
def user_params
  params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
```

**フォーム送信先は自動で選択**
フォームがsubmitされたとき、controllerのcreateメソッドへのPOSTリクエストか、updateメソッドへのPATCHリクエスト、適切な方を自動で選択してくれます。


## form_withヘルパーの仕組み
**html要素の自動生成**
例えば、ここの記述で
```
<%= form_with(model: @user) do |form| %>
　<%= form.label :name %>
　<%= form.text_field :name, class: 'form-control' %>
~~~
```
このようなHTMLが生成される
```
<label for="user_name">Name</label>
<input class="form-control" type="text" value="Example User" name="user[name]" id="user_name">
```
これを見ると、
- form.label によりlabel要素が、
- form.text_fieldによりinput要素(type="text") が生成されていること
 
がわかる。さらに、
- `form_with(model: *@xxx*)`で指定した値が、label要素のfor属性、input要素のname/id属性に反映されていること
- `form.label :*xxx*`がfor属性に反映されていること
- `form.text_field :*xxx*`がname/id属性に反映されていること

もわかる。
なので例えば、下記のように書き換えをすると
```
<%= form_with(model: @user) do |form| %>
　<%= form.label :name %>
　<%= form.text_field :★適当な名前★, class: 'form-control' %>
```
生成されるhtmlもそれに合わせて変わることになる。
```
<label for="user_name">Name</label>
<input class="form-control" type="text" name="user[★適当な名前★]" id="user_★適当な名前★">
```
指定する値の名前をcontrollerの引数（DBのカラム名）に合わせる必要があるのは、ここの名前がそのままname属性に渡ってしまうからということですね。

**フォーム送信先の自動選択**
modelにインスタンスが渡ってきているかで判断してくれる。
- 新規登録画面=modelオプションにインスタンスが渡ってきていない場合はcontroller#createへPOSTリクエスト
- 編集画面＝modelオプションにインスタンスが渡ってきている場合はcontroller#updateへPATCHリクエスト
という形で送り分けてくれる。

```サインアップ画面のフォーム
<form action="/users" accept-charset="UTF-8" method="post">
```
ユーザー編集画面のフォーム
```
<form action="/users/1" accept-charset="UTF-8" method="post">
<input type="hidden" name="_method" value="patch" autocomplete="off">
```
※ HTMLのform要素はGETとPOSTにしか対応していないため、PATCHリクエストするには工夫が必要。railsはname=_methodを用いてサーバ側にリクエストするメソッドを設定可能

## 何が嬉しいのか
所感に近いですが、上記をふまえて以下あたりはありがたいなと思いました。
- 命名規則に従っていればフォーム送信、デフォルト値埋めが何も考えずともできる（これがメイン）
- 各フォーム要素、種別が違ってもほぼ同じ書き方ができるので可読性が上がる
- 記述量が減るのでタイポが減る


## その他
model以外にも様々なオプションがある。
https://railsdoc.com/page/form_with
一部抜粋
| オプション | 例 | 効果 |
|:--|:--|:--|
| scope  | scope: session  | modelを指定しない場合には指定の必要あり。<br>controller#createにデータを読み取ってもらうのに使う（name属性のプレフィックスになる）。<br>例の場合だと、controller側にて`session.xxx`でパラメータを読み取れる|
| url | url: login_path  | modelを指定しない場合に指定の必要あり。<br>サブミット先のパスを指定する。<br>modelを指定している場合は（命名規則に基づき）、該当のモデルの/newないし/editのパスを自動で設定してくれている。  |
|  method | mthod: post  | サブミット時のHTTPメソッドを指定する。<br>普通のmvcの場合はviewがnewかeditかでpostかputか汲み取ってくれるので不要 |

## 参考
[モデルなどからフォームタグを生成 - Railsドキュメント](https://railsdoc.com/page/form_with)
[Railsでform_withでログインする](https://note.com/hikkymouse_pro/n/n9a836181f9d4)
[form_withのlocal: trueって必要なん？これ何なん？（Ruby on Rails）](https://qiita.com/kakudaisuke/items/e032c7705db00e8081dc)

## さいごに
間違いやより正しい表現等を見つけていただけましたら、ご指摘いただけるとありがたいですmm