rails-hotwire
Hotwire (Turbo Drive, Turbo Frames, Turbo Streams, Stimulus)
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Menu
Hotwire (Turbo Drive, Turbo Frames, Turbo Streams, Stimulus)
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Baseado na classificação ocupacional SOC
Export tasks from TodoWrite or feature plans into structured markdown files (epic, user-story, bug, issue) in a tasks/ directory. Use when the user wants to create task files, export tasks, track work in files, or mentions "create tasks", "export", "epic", "user story".
Write, review, and improve Minitest tests for Ruby on Rails applications. Covers model tests, controller tests, system tests, fixtures, and best practices from Rails Testing Guide.
Gather requirements, analyze codebase, and create structured task lists before starting Rails feature development. Use when planning new features, starting development work, breaking down requirements, or when the user mentions "plan", "requirements", "tasks", or "kickoff".
Deploy Rails applications to production using Kamal, Docker, and modern deployment strategies. Covers zero-downtime deployments, environment management, database migrations, SSL/TLS, and production configurations.
Refine and clarify feature requirements by asking targeted follow-up questions, then update or create tasks based on answers. Use after initial planning when requirements need clarification, or when the user mentions "refine", "clarify", "more details", or "questions".
Pagination for Ruby on Rails applications using Kaminari. Use when: (1) Implementing pagination for database records, (2) Building paginated API endpoints, (3) Customizing pagination UI with themes, (4) Handling large datasets efficiently, (5) Creating infinite scroll, (6) Paginating arrays or custom collections, (7) Adding SEO-friendly pagination URLs, (8) Internationalizing pagination labels
| name | rails-hotwire |
| description | Hotwire (Turbo Drive, Turbo Frames, Turbo Streams, Stimulus) |
| version | 1.0.0 |
| rails_version | >= 7.0 |
| tags | ["hotwire","turbo","stimulus","frontend"] |
Hotwire is an alternative approach to building modern web applications without using much JavaScript. It consists of Turbo (Drive, Frames, Streams) and Stimulus.
| Component | Purpose |
|---|---|
| Turbo Drive | Fast page navigation without full reload |
| Turbo Frames | Decompose pages into independent contexts |
| Turbo Streams | Deliver page changes as HTML over WebSocket or in response to form submissions |
| Stimulus | JavaScript sprinkles for enhanced interactivity |
Turbo Drive automatically makes all link clicks and form submissions use AJAX, replacing the page body without full reload.
<%# Turbo Drive is enabled by default in Rails 7+ %>
<%# Links and forms automatically use Turbo Drive %>
<%= link_to "Posts", posts_path %>
<%= link_to "External Site", "https://example.com", data: { turbo: false } %>
<%# Opt-out for specific elements %>
<div data-turbo="false">
<%= link_to "Normal Link", some_path %>
</div>
<%# Programmatic navigation %>
<a href="/posts" data-turbo-action="advance">Posts</a>
<a href="/posts" data-turbo-action="replace">Posts (Replace History)</a>
Turbo Frames allow you to scope navigation and form submissions to a specific part of the page.
<%# app/views/posts/index.html.erb %>
<%= turbo_frame_tag "posts_list" do %>
<%= render @posts %>
<%= link_to "New Post", new_post_path %>
<% end %>
<%# app/views/posts/new.html.erb %>
<%= turbo_frame_tag "posts_list" do %>
<h2>New Post</h2>
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.submit %>
<% end %>
<% end %>
<%# Load content on demand %>
<%= turbo_frame_tag "post_#{post.id}", src: post_path(post), loading: :lazy do %>
<div class="loading">Loading post...</div>
<% end %>
<%# Target a specific frame from outside %>
<%= link_to "Edit", edit_post_path(@post), data: { turbo_frame: "modal" } %>
<%= link_to "Break out of frame", posts_path, data: { turbo_frame: "_top" } %>
<%# Turbo Frame that breaks out by default %>
<%= turbo_frame_tag "navigation", target: "_top" do %>
<%= link_to "Home", root_path %>
<% end %>
Turbo Streams let you update multiple parts of the page in response to actions.
<%# app/views/posts/create.turbo_stream.erb %>
<%# Append to a container %>
<%= turbo_stream.append "posts" do %>
<%= render @post %>
<% end %>
<%# Prepend to a container %>
<%= turbo_stream.prepend "posts" do %>
<%= render @post %>
<% end %>
<%# Replace an element %>
<%= turbo_stream.replace @post do %>
<%= render @post %>
<% end %>
<%# Update content of an element %>
<%= turbo_stream.update "post_#{@post.id}" do %>
<%= render @post %>
<% end %>
<%# Remove an element %>
<%= turbo_stream.remove "post_#{@post.id}" %>
<%# Insert before/after %>
<%= turbo_stream.before "post_#{@post.id}" do %>
<div class="notice">Updated!</div>
<% end %>
<%= turbo_stream.after "post_#{@post.id}" do %>
<div class="ad">Advertisement</div>
<% end %>
class PostsController < ApplicationController
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.turbo_stream
format.html { redirect_to @post }
else
format.turbo_stream { render :form_errors, status: :unprocessable_entity }
format.html { render :new, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @post.update(post_params)
format.turbo_stream
format.html { redirect_to @post }
else
format.turbo_stream { render :form_errors, status: :unprocessable_entity }
format.html { render :edit, status: :unprocessable_entity }
end
end
end
def destroy
@post.destroy
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.remove(@post) }
format.html { redirect_to posts_path }
end
end
end
# app/models/post.rb
class Post < ApplicationRecord
broadcasts_to ->(post) { "posts" }, inserts_by: :prepend
# Or more control
after_create_commit -> { broadcast_prepend_to "posts" }
after_update_commit -> { broadcast_replace_to "posts" }
after_destroy_commit -> { broadcast_remove_to "posts" }
# Broadcast to specific users/channels
after_create_commit -> { broadcast_prepend_to [author, "posts"], target: "posts_list" }
end
<%# Subscribe to broadcasts %>
<%= turbo_stream_from "posts" %>
<div id="posts">
<%= render @posts %>
</div>
Stimulus is a modest JavaScript framework for enhancing static or server-rendered HTML.
// app/javascript/controllers/clipboard_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["source"]
static values = {
message: String,
count: { type: Number, default: 0 }
}
copy() {
navigator.clipboard.writeText(this.sourceTarget.value)
this.countValue++
alert(`Copied! (${this.countValue} times)`)
}
}
<div data-controller="clipboard">
<input data-clipboard-target="source" type="text" value="Copy me!">
<button data-action="click->clipboard#copy">Copy to Clipboard</button>
</div>
// Toggle visibility
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["content"]
toggle() {
this.contentTarget.classList.toggle("hidden")
}
}
<div data-controller="toggle">
<button data-action="click->toggle#toggle">Toggle</button>
<div data-toggle-target="content" class="hidden">
Hidden content
</div>
</div>
// Form autosave
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form", "status"]
async save() {
this.statusTarget.textContent = "Saving..."
const formData = new FormData(this.formTarget)
const response = await fetch(this.formTarget.action, {
method: this.formTarget.method,
body: formData,
headers: {
"X-CSRF-Token": document.querySelector("[name='csrf-token']").content
}
})
if (response.ok) {
this.statusTarget.textContent = "Saved!"
} else {
this.statusTarget.textContent = "Error saving"
}
}
}
<div data-controller="autosave">
<%= form_with model: @post, data: { autosave_target: "form", action: "change->autosave#save" } do |f| %>
<%= f.text_area :content %>
<% end %>
<span data-autosave-target="status"></span>
</div>
data-turbo-confirm for destructive actions<%# Layout %>
<div id="modal" class="modal">
<%= turbo_frame_tag "modal_content" %>
</div>
<%# Link that opens modal %>
<%= link_to "New Post", new_post_path, data: { turbo_frame: "modal_content" } %>
<%# app/views/posts/new.html.erb %>
<%= turbo_frame_tag "modal_content" do %>
<h2>New Post</h2>
<%= render "form", post: @post %>
<% end %>
<%# Show mode %>
<%= turbo_frame_tag "post_#{@post.id}" do %>
<h2><%= @post.title %></h2>
<%= link_to "Edit", edit_post_path(@post) %>
<% end %>
<%# Edit mode %>
<%# app/views/posts/edit.html.erb %>
<%= turbo_frame_tag "post_#{@post.id}" do %>
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.submit "Save" %>
<% end %>
<% end %>
<%= turbo_frame_tag "posts" do %>
<%= render @posts %>
<%= link_to "Load More", posts_path(page: @next_page) %>
<% end %>
form_with not form_fordata-turbo="false" to opt-out