form-helpers について
Rails 8 の form_with と関連ヘルパーを使ったフォーム作成を支援する、Rails 開発者向けスキルです。ネスト属性、ファイルアップロード、ストロングパラメーター、カスタムフォームビルダーを扱います。
主な機能
- 01form_with によるモデル連携・URL指定フォーム
- 02入力・選択・日付フィールドの追加
- 03ネスト属性とストロングパラメーターの処理
- 04ファイルアップロードとリッチテキスト欄の設定
- 05カスタムフォームビルダーの作成
活用シーン
- 01モデル編集フォームや検索フォームの作成
- 02子レコードを含むネストフォームの実装
- 03ファイルアップロードやリッチテキスト入力の追加
- 04旧フォームヘルパーから form_with への移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thinkoodle/ rails-skills --skill form-helpersインストール後、Claude Code で/form-helpersを入力して読み込まれたか確認します。
同梱ファイルreference.md
詳細
Rails Form Helpers Expert
Rails 8 で form_with と関連ヘルパーを使い、適切で最新のフォームを作成します。
このスキルを使う場面
- Rails のビューでフォームを作成するとき(form_with、ネストしたフォーム、セレクト、チェックボックス)
- fields_for と accepts_nested_attributes_for を使ってネストした属性を追加するとき
- 適切なフォームヘルパー(text_field、select、collection_select など)を選ぶとき
- カスタムフォームビルダーを作成するとき
- フォームを Stimulus コントローラーと連携するとき
唯一のルール
使うフォームヘルパーは form_with だけです。以上です。
- ❌
form_for— Rails 5.1 から非推奨です。使用しないでください。 - ❌
form_tag— Rails 5.1 から非推奨です。使用しないでください。 - ✅
form_with— 唯一の正しいフォームヘルパーです。
既存のコードに form_for や form_tag があれば、そのファイルを編集する際に form_with に移行してください。新しいコードを書く場合、ほかのものを使う理由はまったくありません。
form_with の 2 つのモード
モデルに紐づくフォーム(最も一般的)
<%= form_with model: @article do |form| %>
<%= form.text_field :title %>
<%= form.submit %>
<% end %>
Rails がすべてを推測します。アクション URL、HTTP メソッド(新規レコードには POST、保存済みレコードには PATCH)、フィールド名のスコープ(article[title])、送信ボタンのテキスト(「Create Article」または「Update Article」)が自動的に設定されます。
URL に基づくフォーム(モデルなし)
<%= form_with url: search_path, method: :get do |form| %>
<%= form.search_field :query %>
<%= form.submit "Search" %>
<% end %>
検索フォーム、外部 API、またはモデルに紐づかないフォームに使用します。
手順
ステップ 1:フォームの種類を判断する
| シナリオ | 使用するもの |
|---|---|
| モデルレコードの作成・編集 | form_with model: @record |
| 検索フォーム | form_with url: path, method: :get |
| 外部 URL に送信するフォーム | form_with url: "https://...", authenticity_token: false |
| 名前空間付きリソース(例:admin) | form_with model: [:admin, @article] |
| ネストしたリソース | form_with model: [@parent, @child] |
ステップ 2:既存のパターンを確認する
必ず最初に、プロジェクト内の既存フォームを確認してください。
# Find existing form patterns
rg "form_with" app/views/ --type erb
# Check for custom form builders
rg "FormBuilder" app/ --type ruby
# Check for any deprecated form helpers (migration candidates)
rg "form_for\|form_tag" app/views/ --type erb
プロジェクト内の既存の規約に合わせてください。 アプリでカスタムフォームビルダーを使っている場合は、それを使用してください。
ステップ 3:フォームを作成する
フォームビルダーオブジェクトの適切な入力ヘルパーを使用します。すべてのヘルパーでは、最初の引数に属性名を指定します。
よく使う入力ヘルパー:
<%= form_with model: @user do |form| %>
<%= form.text_field :name %>
<%= form.email_field :email %>
<%= form.password_field :password %>
<%= form.telephone_field :phone %>
<%= form.url_field :website %>
<%= form.textarea :bio, size: "70x5" %>
<%= form.number_field :age, in: 18..120 %>
<%= form.range_field :satisfaction, in: 1..10 %>
<%= form.date_field :birthday %>
<%= form.time_field :preferred_time %>
<%= form.datetime_local_field :available_at %>
<%= form.color_field :favorite_color %>
<%= form.hidden_field :referrer, value: "homepage" %>
<%= form.checkbox :terms_accepted %>
<%= form.submit %>
<% end %>
ステップ 4:適切なセレクトヘルパーを使う
セレクトは、エージェントが最も間違えやすい箇所です。パターンの詳細については、リファレンスを確認してください。
静的な選択肢:
<%= form.select :status, ["Draft", "Published", "Archived"] %>
<%= form.select :status, [["Draft", "draft"], ["Published", "published"]] %>
コレクションから選択する(belongs_to):
<%= form.collection_select :city_id, City.order(:name), :id, :name %>
オプショングループを使う場合:
<%= form.grouped_collection_select :city_id, Country.order(:name), :cities, :name, :id, :name %>
prompt/include_blank を使う場合:
<%= form.select :category_id, categories, prompt: "Select a category" %>
<%= form.collection_select :author_id, Author.all, :id, :name, include_blank: "None" %>
ステップ 5:ネストした属性を正しく扱う
Rails のフォームで最も難しい部分です。このパターンに正確に従ってください。
1. モデルを設定する:
class Person < ApplicationRecord
has_many :addresses, inverse_of: :person, dependent: :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true,
reject_if: :all_blank
end
2. コントローラーで空の子レコードを作成する:
def new
@person = Person.new
@person.addresses.build # at least one empty set of fields
end
def edit
@person = Person.find(params[:id])
@person.addresses.build if @person.addresses.empty?
end
3. ネストしたフォームを作成する:
<%= form_with model: @person do |form| %>
<%= form.text_field :name %>
<h3>Addresses</h3>
<%= form.fields_for :addresses do |address_form| %>
<div class="nested-fields">
<%= address_form.hidden_field :id %>
<%= address_form.text_field :street %>
<%= address_form.text_field :city %>
<%= address_form.label :_destroy, "Remove" %>
<%= address_form.checkbox :_destroy %>
</div>
<% end %>
<%= form.submit %>
<% end %>
4. ネストしたパラメーターを許可する:
def person_params
params.expect(person: [
:name,
addresses_attributes: [[:id, :street, :city, :_destroy]]
])
end
ネストしたフォームに関する重要な注意事項:
- 関連付けが空の場合、
fields_forは何も描画しません。少なくとも 1 つの子レコードを必ず作成してください allow_destroy: trueを使うには、_destroyフィールドと、ストロングパラメーターでの:_destroyの許可が必要ですreject_if: :all_blankは、空のネストしたレコードが保存されるのを防ぎます- 既存のレコードには、必ず非表示の
:idフィールドを含めてください(fields_forが自動的に追加します) params.expect内の二重配列[[:id, :street, ...]]は意図的なもので、「これらのキーを持つハッシュの配列」を意味します
ステップ 6:ファイルアップロードを扱う
<%= form_with model: @user do |form| %>
<%= form.file_field :avatar %>
<%= form.submit %>
<% end %>
file_fieldがある場合、form_withはenctype="multipart/form-data"を自動的に設定します- コントローラーでは、
params[:user][:avatar]はActionDispatch::Http::UploadedFileです - 本番環境でのファイル処理には Active Storage を使ってください。独自の仕組みを作らないでください
複数ファイル:
<%= form.file_field :documents, multiple: true %>
配列として許可します:params.expect(user: [documents: []])
ステップ 7:複雑なフォームのストロングパラメーター
単純なモデル:
params.expect(article: [:title, :body, :published])
ネストした属性がある場合:
params.expect(person: [
:name, :email,
addresses_attributes: [[:id, :street, :city, :state, :zip, :_destroy]]
])
配列がある場合(チェックボックス、複数選択):
params.expect(article: [:title, tag_ids: []])
リッチテキストがある場合(Action Text):
params.expect(article: [:title, :body]) # :body is the rich text attribute name
重要な概念
CSRF 保護
GET 以外のすべてのフォームには、authenticity_token の非表示フィールドが自動的に含まれます。これは Rails の CSRF 保護機能です。外部サービスへの送信を除き、無効にしないでください。
<%# External API - disable token %>
<%= form_with url: "https://external.api/webhook", authenticity_token: false do |form| %>
HTTP メソッドのエミュレーション
HTML フォームがサポートするのは GET と POST のみです。Rails は非表示の _method フィールドを使って PATCH、PUT、DELETE をエミュレートします。
<%# This generates method="post" with a hidden _method="patch" %>
<%= form_with model: @article, method: :patch do |form| %>
永続化されたレコードに対して form_with model: @article を使うと、Rails は PATCH を自動的に設定します。
レコードの識別
form_with model: @article は次の内容を判別します。
- 新規レコードの場合 →
/articlesに POST(作成) - 永続化されたレコードの場合 →
/articles/:idに PATCH(更新) - 送信ボタンのテキスト → 「Create Article」または「Update Article」
これには、ルートに resources :articles が必要です。
名前空間付きリソースとネストされたリソース
# Admin namespace
form_with model: [:admin, @article]
# → POST /admin/articles or PATCH /admin/articles/:id
# Nested resource
form_with model: [@magazine, @article]
# → POST /magazines/:magazine_id/articles
# Deep nesting
form_with model: [:admin, @magazine, @article]
カスタムフォームビルダー
同じフォームパターンを繰り返す場合は、カスタムビルダーを作成します。
# app/form_builders/application_form_builder.rb
class ApplicationFormBuilder < ActionView::Helpers::FormBuilder
def text_field(attribute, options = {})
label(attribute) + super(attribute, options.merge(class: "form-input"))
end
end
次のように使います。
<%= form_with model: @user, builder: ApplicationFormBuilder do |form| %>
<%= form.text_field :name %> <%# Automatically includes label + CSS class %>
<% end %>
または、デフォルトで使うためのヘルパーを作成します。
# app/helpers/application_helper.rb
def app_form_with(**options, &block)
options[:builder] = ApplicationFormBuilder
form_with(**options, &block)
end
リッチテキスト(Action Text)
<%= form.rich_text_area :body %>
Action Text のインストール(rails action_text:install)が必要です。属性はモデルで宣言します。
class Article < ApplicationRecord
has_rich_text :body
end
ラジオボタンとチェックボックス
ラジオボタン — ユーザーが1つ選択します。
<%= form.radio_button :flavor, "vanilla" %>
<%= form.label :flavor_vanilla, "Vanilla" %>
<%= form.radio_button :flavor, "chocolate" %>
<%= form.label :flavor_chocolate, "Chocolate" %>
コレクションのラジオボタン — コレクションから選択します。
<%= form.collection_radio_buttons :city_id, City.all, :id, :name %>
チェックボックス — 単一の真偽値です。
<%= form.checkbox :terms_accepted %>
<%= form.label :terms_accepted, "I accept the terms" %>
コレクションのチェックボックス — has_many または HABTM です。
<%= form.collection_checkboxes :interest_ids, Interest.all, :id, :name %>
注:checkbox(check_box ではありません)は値が「0」の非表示フィールドを生成するため、チェックされていない場合も値が送信されます。
日付と時刻のヘルパー
HTML5 ネイティブ入力を優先してください(最新の環境に対応し、モバイルでも使いやすい形式です)。
<%= form.date_field :born_on %>
<%= form.time_field :starts_at %>
<%= form.datetime_local_field :event_at %>
セレクトベースの日付/時刻入力(従来の複数選択ドロップダウンです)。
<%= form.date_select :born_on %>
<%= form.time_select :starts_at %>
<%= form.datetime_select :event_at %>
日付セレクトは、born_on(1i)、born_on(2i)、born_on(3i) のような複数パラメーター属性を生成します。Active Record はこれらを再構成できます。
アンチパターン
form_forまたはform_tagを使う — 常にform_withを使います- ネストされた子要素の作成を忘れる — 関連が空の場合、
fields_forは何も描画しません - ストロングパラメーターに
_destroyがない — チェックボックスがあっても、削除は通知なく失敗します _idのサフィックスなしで belongs_to にselectを使う — フィールド名には外部キーを指定する必要があります- フォームのアクション URL をハードコードする —
model:で推測させるか、ルートヘルパーを使います accepts_nested_attributes_forにallow_destroy: trueを指定し忘れるreject_if: :all_blankを追加しない — 空のネストされたフォームによって空のレコードが作成されます- ファイルアップロード用の
enctypeを手動で設定する —form_withとfile_fieldが処理します - フォームビルダーブロック内で
_tagヘルパーを使う — 代わりにフォームビルダーのメソッドを使います - ネストされた属性で
idを許可しない — 既存のレコードを更新する(重複させない)ために必要です
ネストされたフィールドの動的な追加/削除
ページを再読み込みせずにネストされたフィールドを動的に追加するには、Stimulus を使います。
<%# Render a hidden template for new fields %>
<template data-nested-form-target="template">
<%= form.fields_for :addresses, Address.new, child_index: "NEW_RECORD" do |af| %>
<div class="nested-fields" data-new-record>
<%= af.text_field :street %>
<%= af.text_field :city %>
<%= af.hidden_field :_destroy, value: false, data: { nested_form_target: "destroy" } %>
<button type="button" data-action="nested-form#remove">Remove</button>
</div>
<% end %>
</template>
<div data-nested-form-target="container">
<%= form.fields_for :addresses do |af| %>
<div class="nested-fields">
<%= af.text_field :street %>
<%= af.text_field :city %>
<%= af.hidden_field :_destroy, value: false, data: { nested_form_target: "destroy" } %>
<button type="button" data-action="nested-form#remove">Remove</button>
</div>
<% end %>
</div>
<button type="button" data-action="nested-form#add">Add Address</button>
Stimulus コントローラーは、テンプレート内の NEW_RECORD を一意のタイムスタンプに置き換え、一意なパラメーター名を確保します。Stimulus コントローラーのコード全体については reference.md を参照してください。
クイックリファレンス:入力ヘルパー → HTML タイプ
| ヘルパー | HTML type |
|---|---|
text_field | text |
email_field | email |
password_field | password |
telephone_field | tel |
url_field | url |
search_field | search |
number_field | number |
range_field | range |
date_field | date |
time_field | time |
datetime_local_field | datetime-local |
month_field | month |
week_field | week |
color_field | color |
hidden_field | hidden |
file_field | file |
textarea | <textarea> |
checkbox | checkbox |
radio_button | radio |
フォームのデバッグ
# Check what params your form submits
# In controller:
Rails.logger.debug params.inspect
# Check generated HTML
# In browser dev tools, inspect the <form> element for:
# - action (correct URL?)
# - method (post?)
# - hidden _method field (patch/delete?)
# - authenticity_token present?
# - field names correct (model[attribute]?)
# Common param issues with nested forms:
# - addresses_attributes vs addresses (must be _attributes)
# - Missing id field (creates duplicates instead of updating)
# - _destroy not permitted (silently ignored)