form-auto-save について
Form Auto Save Skill は、変更後に Rails フォームを自動送信する Stimulus パターンを説明します。デバウンスでリクエストを抑え、長文や複数フィールドの編集画面を作る開発者向けです。
主な機能
- 01デバウンス付きフォーム自動送信
- 02既定の待ち時間は 8 秒で変更可能
- 03change と lexxy:change の監視
- 04プログラムからのキャンセルと送信
- 05保存カウントを使ったシステムテスト
活用シーン
- 01長文編集ページへの自動保存の導入
- 02リッチテキストフィールドの変更保存
- 03固定スリープなしでの保存テスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rolemodel/ rolemodel-skills --skill form-auto-saveインストール後、Claude Code で/form-auto-saveを入力して読み込まれたか確認します。
詳細
フォーム自動保存スキル
概要
フォーム自動保存パターンは、入力変更後にデバウンス機構を使ってフォームを自動送信し、サーバーへの過剰なリクエストを防ぎます。これにより、フォームを編集するユーザーにシームレスな「自動保存」体験を提供します。
使用する場面
- ユーザーが自動保存を期待する長文編集インターフェース
- リッチテキストエディターや複数のフィールドを含むフォーム
- ユーザーがページを離れても変更が保持されることを期待する編集ページ
- 明示的に「保存」ボタンをクリックせず、段階的に保存するメリットがあるフォーム
実装
1. Stimulus コントローラー
このパターンでは、自動保存のロジックを処理する Stimulus コントローラー(form-auto-save)を使用します。
コントローラーの配置場所: app/javascript/controllers/form_auto_save_controller.js
主な機能:
- デバウンス時間は8秒です(
static DEBOUNCE_TIMEで設定可能) changeイベントとlexxy:changeイベント(カスタムコンポーネント用)の両方を監視します- パフォーマンス向上のため、パッシブイベントリスナーを使用します
- プログラムから制御するための
cancel()メソッドとsubmit()メソッドを提供します countValueで保存成功数をカウントします。システムスペックはこの値を待機します
コントローラーのコードパターン:
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static DEBOUNCE_TIME = 8000
static values = { count: Number }
connect() {
this.element.addEventListener('change', this.#debounceSubmit.bind(this), { passive: true })
this.element.addEventListener('lexxy:change', this.#debounceSubmit.bind(this), { passive: true })
this.element.addEventListener('turbo:submit-end', ({ detail }) => { if (detail.success) this.countValue++ })
}
cancel() {
clearTimeout(this.debounceTimer)
}
submit() {
this.element.requestSubmit()
}
#debounceSubmit() {
this.#debounce(this.submit.bind(this))
}
#debounce(callback) {
clearTimeout(this.debounceTimer)
this.debounceTimer = setTimeout(callback, this.constructor.DEBOUNCE_TIME)
}
}
2. ビューへの組み込み
Stimulus のデータ属性を使って、コントローラーをフォーム要素に追加します。
必須属性:
data: { controller: 'form-auto-save' }- Stimulus コントローラーを追加しますdata: { turbo_permanent: true }- 必須ではありませんが、Turbo ナビゲーション中のフォーム状態を保持するため、推奨されます
例(Slim):
= simple_form_for resource, html: { data: { controller: 'form-auto-save', turbo_permanent: true } } do |f|
= f.input :field_name
= f.rich_text_area :content
重要な考慮事項
デバウンス時間
- デフォルト:8秒(8000ms)
- 必要に応じて、コントローラーの
static DEBOUNCE_TIMEで調整します - ユーザー体験を考慮してください。短すぎるとリクエストが過剰になり、長すぎると変更が失われる可能性があります
イベントリスナー
changeイベント(標準の HTML 入力変更)を監視しますlexxy:changeイベント(リッチテキストエディターなど、カスタムコンポーネントのイベント)を監視します- スクロールのパフォーマンスを高めるため、パッシブリスナーを使用します
Turbo Permanent
turbo_permanent: trueを指定すると、Turbo ナビゲーション中もフォーム要素が保持されます- ユーザーがページを移動した際に、未保存の変更が失われるのを防ぎます
- デバウンスタイマーを維持するため、自動保存を行うフォームでは特に重要です
フォームのバリデーション
- バックエンドのバリデーションが部分保存を適切に処理できることを確認してください
- すべてのフィールドを必須にするか、部分的な入力を許可するかを検討してください
- 自動保存に失敗した場合は、わかりやすいエラーフィードバックを提供してください
テスト
スリープタイマーではなく、コントローラーの countValue を待機します。Turbo が送信成功を報告すると値が増えるため、デバウンスとリクエストの往復処理の両方を考慮できます。turbo_permanent: true を指定すると、レスポンスによるリダイレクト後もフォームとそのカウントが保持されます。
自動保存ヘルパー
ファイル: spec/support/helpers/form_auto_save_helper.rb
module FormAutoSaveHelper
def expect_auto_save
form = find("[data-controller~='form-auto-save']")
count = form['data-form-auto-save-count-value'].to_i
yield
expect(page).to have_css("[data-form-auto-save-count-value='#{count + 1}']", wait: 10)
end
end
システムスペックでは、spec/support/helpers.rb に c.include FormAutoSaveHelper, type: :system を追加して使用します。wait: は DEBOUNCE_TIME より長くする必要があります。
システムスペックの例
require 'rails_helper'
RSpec.describe 'Form Auto Save', :js do
it 'automatically saves form after changes' do
resource = create(:resource)
visit edit_resource_path(resource)
expect_auto_save do
fill_in 'Field name', with: 'Updated value'
end
expect(resource.reload.field_name).to eq('Updated value')
end
it 'debounces multiple rapid changes' do
resource = create(:resource)
visit edit_resource_path(resource)
expect_auto_save do
fill_in 'Field name', with: 'First'
fill_in 'Field name', with: 'Second'
fill_in 'Field name', with: 'Final'
end
# Should only save once with final value
expect(resource.reload.field_name).to eq('Final')
end
end
よくある問題
問題:フォームが自動保存されない
確認事項:
- コントローラーが正しく追加されていること:
data: { controller: 'form-auto-save' } - フォームフィールドが
changeイベントを発生させていること(テキスト入力では、イベントの発生にフォーカスを外す必要がある場合があります) - ブラウザーの DevTools でネットワークリクエストを確認します
問題:リクエストが多すぎる
解決策:
DEBOUNCE_TIMEを長くします- 不要なイベントが発生していないか確認します
- デバウンスロジックが機能していることを確認します
問題:ページ移動時に変更が失われる
解決策:
- フォームに
turbo_permanent: trueを追加します - フォームに安定した
id属性があることを確認します - 「未保存の変更」に関する警告の追加を検討します
関連パターン
- 動的フォーム: フォームの入力に応じて変化するフィールドには、
dynamic-formsスキル(turbo_form gem)を使用します - Stimulus Values: インスタンスごとにデバウンス時間を設定する必要がある場合
- フォームのバリデーション: 自動保存とインラインバリデーションの併用を検討してください
参考資料
- Stimulus コントローラー API:https://stimulus.hotwired.dev/
- Turbo Permanent:https://turbo.hotwired.dev/handbook/building#persisting-elements-across-page-loads