0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】form_withメソッドってどういう仕組みで動いてるの?

0
Last updated at Posted at 2025-12-31

はじめに

ついに初投稿です!
最初の投稿は、自分がRailsでとてもよく使っている form_with メソッドの仕組みについてまとめてみようと思います。

Railsを勉強していると、

  • form_with って結局なにしてるの?
  • URLとモデルがどう関係してるの?
  • なぜ form_with model: @xxx って書くだけで動くの?

と疑問に思うことがあると思います。(実際に自分がそうでした)

この記事では、form_withメソッドがどのような仕組みで動いているのかにフォーカスして、 実際の自分の個人開発コードを例にしながら解説していきます。

form_withメソッドとは?

form_with は、Railsが用意してくれている ビュー用のヘルパーメソッドです。

一言で言うと、

「モデルと紐づいたフォームを作るためのメソッド」

です。

「モデルに紐づくフォーム」と言われても少し分かりにくいかもしれませんが、要は特定のモデル(テーブル)を編集するためのフォームです。

これにより、

  • フォームに入力された値をモデルの属性に割り当てる
  • 編集、エラー時などにモデルの現在地をフォームに書き戻す(submitボタンを押した際にバリデーションエラーなどが起きても、入力した内容が消えずに残る)

などの処理が自動で行われるので、コード量を最小限に抑えることができます。

例:教材レビュー投稿フォーム

とはいえ文だけだと難しいと思うので、具体例として「使用している学習教材のレビュー(material_review)を投稿するフォーム」を見てみましょう。

レビューは MaterialReview というモデル(テーブル)に保存されます。テーブル構造は以下の通りです。

新規フォーム入力画面

まずは新しいレビュー投稿を作成する、新規フォーム入力画面を見てみましょう。今回は簡略化のため、CSSなどは除外しています。

スクリーンショット 2025-12-30 19.43.53.png

コードは以下の通りです。

<%= form_with model: @material_review do |f| %>
  <div>
    <%= f.label :title, "レビュータイトル" %>
    <%= f.text_field :title %>
  </div>

  <div>
    <%= f.label :material_url, "教材URL" %>
    <%= f.url_field :material_url %>
  </div>

  <div>
    <%= f.label :score, "評価" %>
    <% (1..5).each do |n| %>
      <%= f.radio_button :score, n %>
      <%= n %>
    <% end %>
  </div>

  <div>
    <%= f.label :description, "レビュー内容" %>
    <%= f.text_area :description %>
  </div>

  <%= f.hidden_field :user_id, value: current_user.id %>
  <%= f.submit "レビューを投稿" %>
<% end %>

このように、form_with を使うことで、モデルに対応した入力フォームを簡単に作ることができます。

では具体的に form_with がどういった仕組みになっているかについて見ていきましょう。

モデルの属性に対応した入力要素を設置する

form_withブロックの配下を見ると、f.labelやf.text_field、f.radio_buttonなどのメソッドが呼ばれています。これらはform_withブロック配下で利用できるビューヘルパーで、それぞれモデルに関連付いたラベルやテキストボックス、ラジオボタンなどのフォーム要素を生成します。

たとえば、

<%= f.text_field :title %>

であれば、「MaterialReviewオブジェクトを編集するためのフォームで、このオブジェクトのtitle属性(列)に対応するテキストボックス」 を表します。つまり、ここに記入した値はMaterialReviewオブジェクトのtitle属性の値として当てはめられるということになります。

この1行から生成されるHTMLは、概ね次のようになります。

<input type="text" name="material_review[title]" id="material_review_title" />

name属性にも「material_reviewオブジェクトのtitle属性」という形式で名前がセットされていることが確認できます。他のフォームも同様のため、フォーム送信時のパラメータは最終的に

params = {
  material_review: {
    "title": "...",
    "material_url": "...",
    "score": "...",
    "description": "...",
    "user_id": "..."
  }
}

のようなハッシュ形式でサーバーにデータが渡されます。

new/createアクションメソッド

新規フォームは2つのアクションから構成されています。 

material_reviews_controller.rb
class MaterialReviewsController < ApplicationController
  def new
    @material_review = MaterialReview.new
  end

  def create
    @material_review = MaterialReview.new(material_review_params)
    if @material_review.save
      flash[:notice] = "レビューを投稿しました!"
      redirect_to material_reviews_path
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def material_review_params
      params.expect(material_review: [ :user_id, :title, :material_url, :score, :description ])
    end
end

material_review_paramsというprivateメソッドはStrongParametersと呼ばれるものです。今回は説明を省略しますが、ここではフォームから送られてきたデータを安全に受け取るための仕組みだと考えてください。

「新規フォーム」か「編集フォーム」かで振る舞いが変わる。

ここが form_with の一番重要なポイントです。

<%= form_with model: @material_review do |f| %>

この1行から、Railsは次のようなことを自動で判断しています。

①モデルが「空」か「既存データ」か

今回の新規フォームに繋がっている new アクションでは、コントローラ側で次のように書いています。

material_reviews_controller.rb
def new
  @material_review = MaterialReview.new
end

この時点での @material_review は、

  • データベースにまだ保存されていない
  • id を持っていない

空のモデルインスタンスです。

Railsはこれを見て、

「これは新規作成用のフォームだな」

と判断します。


②自動で決まる送信先URLとHTTPメソッド

@material_review新規レコードの場合、
form_with model: は自動的に次の送信先を選びます。

POST /material_reviews

これは routes.rb

routes.rb
resources :material_reviews

によって定義されている、createアクション用のルーティングです。
つまり、

  • URLを自分で書かなくても
  • methodを指定しなくても

Railsが 「新規作成用フォーム」 として正しい送信先を選んでくれます。

編集フォームのときはどうなる?

次に、既存のレビューを編集する場合を見てみましょう。

material_reviews_controller.rb
def edit
  @material_review = MaterialReview.find(params[:id])
end

先程のnewアクションの部分が、レビューのidをルーティングから受け取るeditメソッドに変化したと思ってください。このときの @material_review は、

  • すでにデータベースに存在している
  • id を持っている

既存データのモデルインスタンスです。

editメソッドの遷移先のビュー側のコードは、新規作成時とほぼ同じです。

<%= form_with model: @material_review do |f| %>
   •
   •
   •
  <%= f.submit "更新する" %>
<% end %>

しかし、Railsは内部的に次のように判断します。

  • これは新規作成ではない
  • すでに id を持っている、既存データのモデルだ
  • つまり「更新用フォーム」だ

その結果、フォームに入力されたデータの送信先は自動的に

PATCH /material_reviews/:id

となり、先程は create メソッドを呼び出されていたのと打って変わって、update アクションが呼ばれます。

newとeditの違いまとめ

状態 送信先URL HTTPメソッド
new /material_reviews POST
edit /material_reviews/:id PATCH

同じ form_with model: を使っているのに、
Railsが自動で切り替えてくれる
のが分かると思います。

モデルの編集目的ではないフォームも作成できる?

結論からいうと可能です。たとえば、

  • 検索フォーム
  • ログインフォーム
  • お問い合わせフォーム

など、特定のモデルを作成・更新しないフォームの場合です。

このようにmodelオプション(モデル)が存在しない場合、ポスト先も自動では決まらなくなるので、urlオプション(ポスト先) が必須になります。

<%# 検索フォームの例 %>

<%= form_with url: "/view/search" do |f| %>
  <%= f.text_field :keyword %>
  <%= f.submit "検索" %>
<% end %>

form_withメソッドは特に指定がない限りデフォルトでHTTPメソッドはPOSTに設定されているため、今回の場合だと

routes.rb
post 'view/search'

を探しに行きます。HTTPメソッドを適宜変更したい場合は methodオプション を付け加えてください。

<%= form_with url: "/view/keyword", method: :get do |f| %>

この場合だと、

routes.rb
get 'view/keyword'

というルーティングを探しに行きます。

まとめ

  • form_withモデルを中心にフォームを組み立てる仕組み

  • model: @xxx を指定すると

    • 送信先URL
    • HTTPメソッド
    • paramsの構造

をRailsが自動で決めてくれる

  • モデルが空か(new)、既存か(edit)で挙動が切り替わる

  • モデルに紐づかないフォームでは url: を使う

「フォームはモデルの状態を見て動いている」と理解できると、form_with はとても扱いやすくなると思います!

おわりに

初めての投稿で至らない点もあったかと思いますが、ここまで読んでいただきありがとうございました!今後も記事にしたいと思ったことを投稿していくので、また読んでいただけると嬉しいです!一緒に頑張っていきましょう!

(ご指摘等ありましたら遠慮なく伝えていただけますと幸いです🙇‍♀️)

参考

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?