skills/turbo-fetch/SKILL.md
Implement dynamic form updates using Turbo Streams and Stimulus. Use when forms need to update fields based on user selections without full page reloads, such as cascading dropdowns, conditional fields, or dynamic option lists.
npx skillsauth add rolemodel/rolemodel-skills turbo-fetchInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill documents the turbo fetch pattern for dynamically updating form fields based on user input using Turbo Streams and Stimulus.
Use turbo fetch when you need to:
The turbo fetch pattern consists of four components:
In config/routes.rb, ensure the turbo_fetch concern exists:
concern :turbo_fetch do
patch :turbo_fetch, on: :collection
end
Then apply it to your resource:
resources :materials, concerns: %i[turbo_fetch]
This creates a route: PATCH /materials/turbo_fetch
Add a turbo_fetch action to your controller:
class MaterialsController < ApplicationController
def turbo_fetch
@material = authorize Material.new(material_params)
# The view will handle the turbo stream responses
end
private
def material_params
params.require(:material).permit(:type, :substance, ...)
end
end
Key points:
Create app/views/[resource]/turbo_fetch.turbo_stream.slim:
= simple_form_for @material do |f|
= turbo_stream.update 'substance-field' do
= f.input :substance, collection: @material.substances
= turbo_stream.replace 'material_details', partial: dimension_fields_partial_path, locals: { f: }
Available turbo stream actions:
turbo_stream.update - Replace the content inside an elementturbo_stream.replace - Replace the entire elementturbo_stream.append - Add content at the endturbo_stream.prepend - Add content at the beginningturbo_stream.remove - Remove an elementAdd the Stimulus controller to your form:
= simple_form_for resource, data: { controller: 'turbo-fetch', turbo_fetch_url_value: turbo_fetch_materials_url } do |f|
.form-row
= f.input :type, input_html: { data: { action: "turbo-fetch#perform" } }
#substance-field.flexible
= f.input :substance, collection: f.object.substances
#material_details
= render dimension_fields_partial_path, f: f
Key attributes:
data-controller="turbo-fetch" - Activates the Stimulus controllerdata-turbo-fetch-url-value - The URL to PATCH (defaults to form action + /turbo_fetch)data-action="turbo-fetch#perform" - Triggers the fetch on field changeThe turbo_fetch_controller.js should exist at app/javascript/controllers/turbo_fetch_controller.js:
import { Controller } from '@hotwired/stimulus'
import { patch } from '@rails/request.js'
export default class extends Controller {
static values = { url: String, count: Number }
async perform({ params: { url: urlParam, query: queryParams } }) {
const body = new FormData(this.element)
if (queryParams) Object.keys(queryParams).forEach(key => body.append(key, queryParams[key]))
const response = await patch(urlParam || this.urlValue, { body, responseKind: 'turbo-stream' })
if (response.ok) this.countValue += 1
}
}
Route:
resources :materials, concerns: %i[duplication turbo_fetch]
Controller:
def turbo_fetch
@material = authorize Material.new(material_params)
end
View (turbo_fetch.turbo_stream.slim):
= simple_form_for @material do |f|
= turbo_stream.update 'substance-field' do
= f.input :substance, collection: @material.substances, as: :tom_select, allow_create: true
= turbo_stream.replace 'material_details', partial: dimension_fields_partial_path, locals: { f: }
Form:
= simple_form_for resource, data: { controller: 'turbo-fetch', turbo_fetch_url_value: turbo_fetch_materials_url } do |f|
= f.input :type, input_html: { data: { action: "turbo-fetch#perform" } }
#substance-field.flexible
= f.input :substance, collection: f.object.substances
#material_details
= render dimension_fields_partial_path, f: f
When selecting a type, update available options in another field:
data-action="turbo-fetch#perform"#substance-field)Show/hide entire form sections based on selection:
turbo_stream.replace to swap out entire sectionsMost turbo_fetch routes are on :collection, but for nested resources with IDs:
resources :custom_parts, concerns: %i[turbo_fetch] do
patch :turbo_fetch, on: :member # For child items with IDs
end
simple_form_for to maintain form contextUpdates not appearing:
rails routes | grep turbo_fetch)Wrong data in fields:
material_params (or equivalent)Authorization errors:
turbo_fetch action runs same authorization as new/createtesting
Verify what Ruby versions actually exist and install a specific Ruby via rbenv. Use BEFORE asserting that any Ruby version does or doesn't exist (e.g., "Ruby 4.0 isn't out yet", "the latest Ruby is 3.x", "Ruby X.Y.Z doesn't exist"). Also use when the user asks "what's the latest Ruby", "is Ruby X out", "does Ruby X.Y exist", "install Ruby", "switch to Ruby X", "what Ruby is installed", or mentions a specific Ruby version you're unsure about. Claude's training data may be out of date — run `check.sh` first.
development
Trace code through the stack — upward to entry points, downward to data, or laterally across boundaries. Use when the user asks "where does this get called from", "what calls this method", "trace this through the stack", "how does this request flow", "where does this data come from", "follow this through the code", or pastes/selects a piece of code and wants to understand where it fits in the larger system.
tools
Pick the single highest-priority unresolved Sentry issue and hand it off to a fixer skill. Use when triaging Sentry errors, running automated issue triage, or when asked to fix the top Sentry issue in a project.
tools
Find and fix issues from Sentry using MCP. Use when asked to fix Sentry errors, debug production issues, investigate exceptions, or resolve bugs reported in Sentry. Methodically analyzes stack traces, breadcrumbs, traces, and context to identify root causes.