5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

form_withのまとめ

Last updated at Posted at 2023-04-26

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を指定してあげる必要がある。(この後説明します。)
ここを適切にやらないと以下のエラーがにハマっちゃいます。

form_withの応用

応用といってもそんなに難しくありません。多分使っていたら、こっちのアクションに飛ばしたいなーとか思い始めると思います。そんなときには、form_withオプションの使用を検討しましょう。

オプション 説明 デフォルト値
:model モデルを指定
:scope スコープを指定
:url URLを指定
:local リモート送信の無効 false
:namespace 名前空間を指定
:method HTTPメソッドを指定 POST
:id id属性を指定
:class class属性を指定

この中でも特に重要なのが、以下の4点です。

  • model(説明済み)
  • url
  • method
  • scope

今回url/method/scopeの3つに絞って説明していきます。
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 リソースの更新をサーバーに伝える
PUTリクエストは、更新対象のリソースを完全に置き換える
PATCH PUTリクエストと同様に、リソースの更新をサーバーに伝える
更新対象のリソースを部分的に更新する
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>

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が1番に対して作成するわけです。

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

以下が生成される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の問題であったり、

rails7の場合、書き方が違ったりなど別問題の時もあるので、その場合は他の原因も探ってみましょう。

data: { "turbo-method": :post }

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?