form_withのまとめ

# form_withの基本
form_withは非常によく使うヘルパーですが、基本的な挙動がわかっていないとハマってしまうので、ぜひ理解したいところです。


一番基本的な書き方は以下の書き方でしょうか。（今回のUserモデルのカラムはnameとemailだけを想定）
```
<%= form_with model: @user do |f| %>
    <!--フォーム内容 -->
<% end %>
```
以下のように直接書くこともできます。
```
<%= form_with model: User.new do |f| %>
    <!--フォーム内容 -->
<% end %>
```

```users_controller.rb
.
.
.
def new
  @user = User.new
end

def create
  @user = User.new(user_params)
  if @user.save
    redirect_to user_path(@user)
  else
    render :new
  end

def edit
  @user = User.find(params[:id])
end

def update
  @user = User.find(params[:id])
  if @user.update(user_params)
    redirect_to request.referer
  else
    render :new
  end
end

def destroy
  @user = User.find(params[:id])
  @user.destroy
  redirect_to request.referer
end

  private
    def user_params
      params.require(:user).permit(:name, :email)
.
.
.
```

form_withを先ほどのように書くと、railsではデフォルトとして、
Userモデルのnew=>createとよきに計らって勝手にレールを準備してくれる。

今回の場合、railsは
「ふむふむ、User.newということは、データが空だからcreateアクションに飛ばせばいいんだな」と判断してくれる。



ただし、modelが存在しない場合はurlとscopeを指定してあげる必要がある。（この後説明します。）
ここを適切にやらないと以下のエラーがにハマっちゃいます。

https://qiita.com/F_Yoko/items/f432795176644e4303e4

# form_withの応用
応用といってもそんなに難しくありません。多分使っていたら、こっちのアクションに飛ばしたいなーとか思い始めると思います。そんなときには、form_withオプションの使用を検討しましょう。
| オプション | 説明	 | デフォルト値 |
|:-:|:-:|:-:|
|:model   |モデルを指定|   |
|:scope   |スコープを指定|   |
|:url   |URLを指定   |   |
|:local |リモート送信の無効   |false  |
|:namespace	|名前空間を指定   |   |
|:method   |HTTPメソッドを指定   |POST   |
|:id   |id属性を指定	   |   |
|:class |class属性を指定   |   |

https://railsdoc.com/page/form_with

この中でも特に重要なのが、以下の4点です。
* model（説明済み）
* url
* method
* scope

今回url/method/scopeの３つに絞って説明していきます。
localについては、ajaxの記事を書く時に説明する予定です。
また他はそのままの意味なので適宜使い方を調べてください。

## url
urlとはpathのことだと思っても大丈夫です。

先ほど以下のような基本的な書き方を紹介しました。
```
<%= form_with model: @user do |f| %>
    <!--フォーム内容 -->
<% end %>
```
しかし、これだとrailsが勝手にレールに沿ってパラメータを送るので融通が効きません。
「なんか思ったところに飛ばないな」とか「確認画面を作りたいな」とかいう時に使います。

urlはこんな感じで書きます。
```
<%= form_with url: users_path, method: :post do |f| %>
    <!-- フォーム内容 -->
<% end %>
```

一言でまとめると、「controllerのこのアクションに飛ばしたい」というときに、そのパスとメソッドを指定しましょう。


## method
これはそのままなのですが、httpはWeb上の情報を伝達するための決まり事です。
で、そのリクエストの方法にget/post/put/patch/deleteがあります。

つまり、どのようなことをしたいかによって、このメソッドを指定しないといけないわけです。

methodを簡単にまとめると、以下の通りです。

| リクエスト（method） | 説明 |
|:-:|:-:|
| GET  |特定のリソースを取得する|
| POST |サーバーに新しいリソースを作成する|
| PUT|リソースの更新をサーバーに伝える<br>PUTリクエストは、更新対象のリソースを完全に置き換える|
| PATCH  |PUTリクエストと同様に、リソースの更新をサーバーに伝える<br>更新対象のリソースを部分的に更新する   |
| DELETE  |サーバーからリソースを削除する |

で、これを指定してやるわけです。

時々、POSTのパスに飛ばしているのに、GETに飛ぶこともあるので、明示的に指定しましょう。

## scope 
scopeについては、以下を比較すればわかりやすいです。
* __scopeを使わずにurlでpathを指定した場合__
* __scopeを使ってurlでpathを指定した場合__


*__scopeを使わずにurlでpathを指定した場合__
```
<%= form_with url: users_path, local: true do |f| %>
  <%= f.label :name, "name" %>
  <%= f.text_field :name %>
  <%= f.submit "submit" %>
<% end %>
```
これから生成されるHTMLは次のようになります。（重要部分だけ抽出）
```
<form action="/users" accept-charset="UTF-8" method="post">
省略
  <label for="name">name</label>
  <input type="text" name="name" id="user">
省略
</form>
```

*__scopeを使ってurlでpathを指定した場合__
```
<%= form_with url: users_path, scope: :user, local: true do |f| %>
  <%= f.label :user, "name" %>
  <%= f.text_field :name %>
  <%= f.submit "submit" %>
<% end %>
```
生成されるHTMLはこうなります。（重要部分だけ抽出）
```
<form action="/posts" accept-charset="UTF-8" method="post">
省略
  <label for="user_name">name</label>
  <input type="text" name="user[name]" id="user">
省略
</form>
```
:::note
labelのfor属性、inputのid属性がnameからuser_titleに変わり、name属性がnameからuser[name]に変わりました。
:::

つまり、scopeを使うことで送られるパラメーターの構造が変わります。したがって、これを考慮しないとストロングパラメータで拒否されるわけですね。

・__scopeを設定していない場合__
params[:name]でnameを取得できる
・__scope: userを設定した場合__
params[:user][:name]でnameを取得できる


ちなみにモデルのインスタンスを与えた場合は、scopeを設定しなくても初めからグループ化されています。
つまり、インスタンスを与えない場合は、このscopeの指定の仕方に気をつけないといけないわけですね。

# ルーティングでネストしている場合

ルーティングでネストを定義している時は書き方が少し変わります。
ただし、基本を押さえれば全然難しくありません。

ユーザーにコメントを投稿するフォームを例に挙げてみます。

まず、コントローラは以下のように書きます。
```users_controller.rb
def new
  @user = User.find(params[:user_id])
  @comment = Comment.new
end

def edit
  @user = User.find(params[:user_id])
  @comment = Comment.find(params[:id])
end
```
で、ユーザーの個別ページには以下のようにform_withを書きます。
個別ページなのでurlは"/users/1"みたいになっています。

```users/show.html.erb
<%= form_with model: [@user, @comment] do |form| %>
  <%= form.text_field :text %>
  <%= form.submit %>
<% end %>
```

しかし、今回はform_withの書き方が少し違いますね。
```
model: [@user, @comment]
```
となっています。

これはどういうことか？コントローラを見ればわかります。
今回は"/users/1"のページにいるので、user_idが1番だとわかります。
つまり、@userはidが一番のユーザーに決まります。

で、それに対して、Comment.newで、コメントをユーザーのidが１番に対して作成するわけです。

つまり、「idが１番のユーザーに対して、新しいコメントを作成するよ」ということを
@userと@commentの２つで示しているわけです。


以下が生成されるhtmlです。
updateについても同様に考えれば良いので割愛します。ぜひ、editアクションはこのような書き方をするのか考えてみてください。
```
# create時の例
<form action="/users/1/comments" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" />

# update時の例
<form action="/users/1/comments/1" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="_method" value="patch" />
```

# メソッドの一覧
form_withで使えるメソッドはたくさんあります。一つひとつ使って挙動を確認してみるのがいいですね。
個人的には太字にした部分が頻繁に使ったり、重要なものだと思います。他も使ったりしますが、個人的な使用頻度は低めです。
| メソッド | 説明 |
|:-:|:-:|
| __form.text_field__	  |  一行のテキスト投稿フォーム |
| __form.text_area__  | 複数行のテキスト投稿フォーム  |
|form.number_field   |数値入力ボックスを生成   |
|__form.search_field__	  |	一行の検索フォーム   |
|__form.email_field__  |メールアドレス入力ボックスを生成   |
|__form.check_box__   |データベースの情報を使わずにチェックボックスを生成   |
|form.collection_check_boxes|データベースの情報を元にチェックボックスを生成 |
|form.select   |選択肢を作成   |
|form.collection_select   |データベースの情報を元に選択肢を生成   |
|__form.file_field__	   |ファイル選択ボックスを生成   |
|form.datetime_field   | 日時の入力欄を生成  |
|form.date_select  |日付選択ボックスを生成   |
|__form.hidden_field__   |非表示のフォーム   |
|__form.submit__|送信ボタンの生成   |
	
	

# まとめ
以上がform_withのまとめでした。

まとめると、
基本的には基本形を使用しましょう。
___form_with model: model_instance___ 

で、以下のような時には、scope/method/urlを駆使していきましょう。
* __「パラメータが思ったところに飛ばない」__
* __「他のところに飛ばしたい」__


補足ですが、思ったところに飛ばない原因がform_withの書き方以前に、
rails6の場合、rails_ujsの問題であったり、

https://blog.ezic.info/43631.html

rails7の場合、書き方が違ったりなど別問題の時もあるので、その場合は他の原因も探ってみましょう。
```
data: { "turbo-method": :post }
```
https://zenn.dev/tama8021/articles/22_1130_rails7_link_to_medthod_post