Curated library of 39 AI agent skills for Ruby on Rails development. Organized by category: planning, testing, code-quality, ddd, engines, infrastructure, api, patterns, context, orchestration, and workflows. Includes 5 callable workflow skills (rails-tdd-loop, rails-review-flow, rails-setup-flow, rails-quality-flow, rails-engines-flow) for complete development cycles. Covers code review, architecture, security, testing (RSpec), engines, service objects, DDD patterns, and TDD automation.
95
98%
Does it follow best practices?
Impact
95%
1.20xAverage score across 35 eval scenarios
Passed
No known issues
Complete code examples for Turbo and Stimulus implementations.
<!-- app/views/posts/index.html.erb -->
<h1>Posts</h1>
<%= turbo_frame_tag "posts_list" do %>
<%= render @posts %>
<% end %><!-- app/views/posts/index.html.erb -->
<%= turbo_frame_tag "new_post" do %>
<%= link_to "New Post", new_post_path %>
<% end %>
<%= turbo_frame_tag "posts_list" do %>
<%= render @posts %>
<% end %><!-- app/views/posts/new.html.erb -->
<%= turbo_frame_tag "new_post" do %>
<%= render "form", post: @post %>
<% end %><!-- Load content when frame scrolls into view -->
<%= turbo_frame_tag "comments", src: post_comments_path(@post), loading: :lazy do %>
<p>Loading comments...</p>
<% end %><!-- app/views/posts/create.turbo_stream.erb -->
<%= turbo_stream.append "posts_list", partial: "post", locals: { post: @post } %>
<%= turbo_stream.update "new_post", partial: "posts/new_link" %>
<%= turbo_stream.update "post_count", Post.count %><!-- app/views/posts/update.turbo_stream.erb -->
<%= turbo_stream.replace @post, partial: "post", locals: { post: @post } %><!-- app/views/posts/destroy.turbo_stream.erb -->
<%= turbo_stream.remove @post %>
<%= turbo_stream.update "post_count", Post.count %># app/models/post.rb
class Post < ApplicationRecord
after_create_commit -> { broadcast_append_to "posts" }
after_update_commit -> { broadcast_replace_to "posts" }
after_destroy_commit -> { broadcast_remove_to "posts" }
end<!-- app/views/posts/index.html.erb -->
<%= turbo_stream_from "posts" %>
<%= turbo_frame_tag "posts_list" do %>
<%= render @posts %>
<% end %>// app/javascript/controllers/clipboard_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["source", "button"]
copy() {
navigator.clipboard.writeText(this.sourceTarget.value)
this.buttonTarget.textContent = "Copied!"
setTimeout(() => {
this.buttonTarget.textContent = "Copy"
}, 2000)
}
}<!-- app/views/posts/show.html.erb -->
<div data-controller="clipboard">
<input data-clipboard-target="source" type="text" value="<%= post_url(@post) %>" readonly>
<button data-action="clipboard#copy" data-clipboard-target="button">Copy</button>
</div>// app/javascript/controllers/slideshow_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["slide"]
static values = { index: Number }
next() {
this.indexValue++
}
previous() {
this.indexValue--
}
indexValueChanged() {
this.showCurrentSlide()
}
showCurrentSlide() {
this.slideTargets.forEach((slide, index) => {
slide.hidden = index !== this.indexValue
})
}
}<div data-controller="slideshow" data-slideshow-index-value="0">
<button data-action="slideshow#previous">←</button>
<button data-action="slideshow#next">→</button>
<div data-slideshow-target="slide">🐵</div>
<div data-slideshow-target="slide" hidden>🙈</div>
<div data-slideshow-target="slide" hidden>🙉</div>
<div data-slideshow-target="slide" hidden>🙊</div>
</div>// app/javascript/controllers/form_validation_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input", "error"]
validate() {
if (this.inputTarget.value.length < 5) {
this.errorTarget.textContent = "Must be at least 5 characters"
} else {
this.errorTarget.textContent = ""
}
}
}<%= form_with model: @post, data: { controller: "form-validation" } do |f| %>
<%= f.text_field :title,
data: {
form_validation_target: "input",
action: "input->form-validation#validate"
} %>
<span data-form-validation-target="error" class="error"></span>
<% end %><!-- Step 1: Plain HTML (works without JS) -->
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body %>
<%= f.submit "Create Post" %>
<% end %><!-- Step 2: Wrap in frame for partial updates -->
<%= turbo_frame_tag "post_form" do %>
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body %>
<%= f.submit "Create Post" %>
<% end %>
<% end %># Step 3: Add controller response
class PostsController < ApplicationController
def create
@post = Post.new(post_params)
if @post.save
respond_to do |format|
format.turbo_stream
format.html { redirect_to @post }
end
else
render :new, status: :unprocessable_entity
end
end
endReal-time updates via WebSocket:
# app/models/post.rb
class Post < ApplicationRecord
broadcasts_to ->(post) { [post.board, "posts"] }, inserts_by: :prepend
endSurgical DOM updates (Turbo 8+):
<%= turbo_stream.morph @post, partial: "posts/post", locals: { post: @post } %>Scoped navigation with nested turbo frames:
<%= turbo_frame_tag "board" do %>
<%= turbo_frame_tag "post_#{@post.id}" do %>
<%= link_to "Edit", edit_post_path(@post) %>
<% end %>
<% end %>Configurable, CSS-decoupled controllers:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = { url: String, delay: { type: Number, default: 300 } }
static classes = ["active"]
connect() {
this.element.classList.add(this.activeClass)
console.log("Fetching from", this.urlValue, "after", this.delayValue, "ms")
}
}<div data-controller="loader"
data-loader-url-value="/posts"
data-loader-delay-value="500"
data-loader-active-class="is-loading">
</div>docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
mcp_server
skills
api
api-rest-collection
rails-graphql-best-practices
code-quality
rails-architecture-review
rails-code-conventions
rails-code-review
rails-review-response
rails-security-review
rails-stack-conventions
assets
snippets
refactor-safely
context
rails-context-engineering
rails-project-onboarding
ddd
ddd-boundaries-review
ddd-rails-modeling
ddd-ubiquitous-language
engines
rails-engine-compatibility
rails-engine-docs
rails-engine-extraction
rails-engine-installers
rails-engine-release
rails-engine-reviewer
rails-engine-testing
infrastructure
rails-api-versioning
rails-background-jobs
rails-database-seeding
rails-frontend-hotwire
rails-migration-safety
rails-performance-optimization
orchestration
rails-skills-orchestrator
patterns
ruby-service-objects
strategy-factory-null-calculator
yard-documentation
planning
create-prd
generate-tasks
ticket-planning
testing
rails-bug-triage
rails-tdd-slices
rspec-best-practices
rspec-service-testing