mcpField
F
form-helpers

form-helpers

github.com/thinkoodle開発7187 回インストール8 週間前に更新
リポジトリ

form-helpers について

Rails 8 の form_with と関連ヘルパーを使ったフォーム作成を支援する、Rails 開発者向けスキルです。ネスト属性、ファイルアップロード、ストロングパラメーター、カスタムフォームビルダーを扱います。

主な機能

  1. 01form_with によるモデル連携・URL指定フォーム
  2. 02入力・選択・日付フィールドの追加
  3. 03ネスト属性とストロングパラメーターの処理
  4. 04ファイルアップロードとリッチテキスト欄の設定
  5. 05カスタムフォームビルダーの作成

活用シーン

  1. 01モデル編集フォームや検索フォームの作成
  2. 02子レコードを含むネストフォームの実装
  3. 03ファイルアップロードやリッチテキスト入力の追加
  4. 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 はこれらを再構成できます。

アンチパターン

  1. form_for または form_tag を使う — 常に form_with を使います
  2. ネストされた子要素の作成を忘れる — 関連が空の場合、fields_for は何も描画しません
  3. ストロングパラメーターに _destroy がない — チェックボックスがあっても、削除は通知なく失敗します
  4. _id のサフィックスなしで belongs_to に select を使う — フィールド名には外部キーを指定する必要があります
  5. フォームのアクション URL をハードコードする — model: で推測させるか、ルートヘルパーを使います
  6. accepts_nested_attributes_for に allow_destroy: true を指定し忘れる
  7. reject_if: :all_blank を追加しない — 空のネストされたフォームによって空のレコードが作成されます
  8. ファイルアップロード用の enctype を手動で設定する — form_with と file_field が処理します
  9. フォームビルダーブロック内で _tag ヘルパーを使う — 代わりにフォームビルダーのメソッドを使います
  10. ネストされた属性で 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_fieldtext
email_fieldemail
password_fieldpassword
telephone_fieldtel
url_fieldurl
search_fieldsearch
number_fieldnumber
range_fieldrange
date_fielddate
time_fieldtime
datetime_local_fielddatetime-local
month_fieldmonth
week_fieldweek
color_fieldcolor
hidden_fieldhidden
file_fieldfile
textarea<textarea>
checkboxcheckbox
radio_buttonradio

フォームのデバッグ

# 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)

コメント