Viewの基本: form_with を使って安全なフォームを作成する方法
はじめに
Webアプリケーションにおいて、ユーザーからのデータを受け取るためのHTMLフォームは不可欠な要素です。Ruby on Railsでは、フォームを簡単かつ安全に作成するための強力なヘルパーメソッドform_withが提供されています。
この記事では、form_withの基本的な使い方から、その裏側で何が行われているのか、そしてなぜそれが「安全」なのかについて詳しく解説します。form_forやform_tagに慣れている方も、モダンなRails開発の標準であるform_withへの理解を深めていきましょう。
form_withとは?
form_withは、Rails 5.1から導入された統一的なフォームヘルパーです。それ以前に存在したform_for(モデルオブジェクトに紐づくフォーム用)とform_tag(特定のモデルに紐づかないフォーム用)の機能を統合し、よりシンプルで柔軟な記述が可能になりました。
1. モデルに紐づくフォームの作成 (modelオプション)
最も一般的な使い方は、Active Recordのモデルオブジェクトと連携させる方法です。新規作成(new)と編集(edit)で同じフォームテンプレートを共有できるのが大きなメリットです。
コントローラ
まず、コントローラでフォームが使用するオブジェクトを用意します。
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def new
@article = Article.new # 新規作成用の空のオブジェクト
end
def edit
@article = Article.find(params[:id]) # 編集対象のオブジェクト
end
# ... create, updateアクションなど
endビュー (_form.html.erb)
次に、ビューでform_withを使ってフォームを描画します。
<%# app/views/articles/_form.html.erb %>
<%= form_with(model: @article) do |form| %>
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :content %><br>
<%= form.text_area :content %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>このフォームはnewとeditの両方のアクションからパーシャルとして呼び出せます。
form_withの賢い挙動
ここでform_withは、渡された@articleオブジェクトの状態を自動で判別します。
@articleが新規オブジェクトの場合 (@article.persisted?がfalse)- フォームの
action属性は/articlesになります。 - HTTPメソッドは
POSTになります。 submitボタンのテキストは "Create Article" になります。
- フォームの
@articleがデータベースに保存済みのオブジェクトの場合 (@article.persisted?がtrue)- フォームの
action属性は/articles/1のような具体的なパスになります。 - HTTPメソッドは
PATCHになります。(HTMLの<form>タグにはmethod="post"と出力されますが、内部に<input type="hidden" name="_method" value="patch">が生成され、Railsがこれを解釈します) submitボタンのテキストは "Update Article" になります。
- フォームの
このように、オブジェクトの状態に応じて適切なリクエスト先とメソッドを自動で設定してくれるため、開発者はロジックを共通化できるのです。
2. モデルに紐づかないフォームの作成 (urlオプション)
検索フォームのように、特定のモデルオブジェクトと直接関連しないフォームを作成する場合は、urlオプションで送信先のパスを明示的に指定します。
<%# 検索フォームの例 %>
<%= form_with(url: search_path, method: :get) do |form| %>
<%= form.label :query, "Search for:" %>
<%= form.text_field :query %>
<%= form.submit "Search" %>
<% end %>url: search_path: フォームの送信先をsearch_path(rails routesで確認できる名前付きルート)に設定します。method: :get: 検索なので、HTTPメソッドをGETに指定しています。これにより、検索クエリがURLのクエリパラメータ(例:/search?query=Rails)として送信されます。
3. なぜform_withは安全なのか? - CSRF対策
form_with(およびRailsのフォームヘルパー全般)が生成するHTMLをよく見ると、見慣れないhiddenタイプのinputタグが2つ含まれていることに気づきます。
<form action="/articles" method="post">
<!-- ... form fields ... -->
<!-- 1. UTF-8エンコーディング強制 -->
<input type="hidden" name="utf8" value="✓">
<!-- 2. CSRFトークン -->
<input type="hidden" name="authenticity_token" value="GENERATED_TOKEN">
</form>このうち、セキュリティ上非常に重要なのが**authenticity_tokenです。これはCSRF(クロスサイト・リクエスト・フォージェリ)**という種類の攻撃を防ぐためのものです。
CSRF攻撃とは?
- 悪意のある攻撃者が、罠サイトに「あなたのサイトのデータを削除するリクエスト」を送信するリンクやフォームを仕掛ける。
- あなたのサイトにログイン中のユーザーが、その罠サイトを訪れてリンクをクリックしてしまう。
- ユーザーのブラウザは、ログイン情報(Cookie)を保持したまま、あなたのサイトに意図しないリクエストを送信してしまう。
- あなたのサイトは正規のユーザーからのリクエストだと誤認し、データが不正に操作されてしまう。
CSRFトークンによる防御
Railsは、form_withでフォームを生成する際に、セッションごとにユニークなauthenticity_tokenを埋め込みます。そして、POST, PATCH, DELETEなどのデータ変更を伴うリクエストを受け取った際に、送信されてきたトークンがサーバー側で保持している正しいトークンと一致するかを検証します。
悪意のあるサイトは、この正しいトークンを知ることができないため、偽のリクエストを送信してもRails側でブロックされます。form_withを使うだけで、この重要なセキュリティ対策が自動的に施されるのです。
まとめ
form_withは、Railsにおけるフォーム作成の標準的な方法です。
model: @objectを使えば、新規作成と編集でロジックを共通化できる。url: pathを使えば、モデルに依存しないフォームも簡単に作れる。- オブジェクトの状態を判別し、適切な
actionとmethodを自動で設定してくれる。 - CSRF対策が自動的に組み込まれており、安全なフォームを構築できる。
フォームを作成する際は、手で<form>タグを書くのではなく、常にform_withを利用することを心がけましょう。これにより、コードの可読性が向上し、アプリケーションのセキュリティも確保されます。