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 examples for identifying and fixing performance bottlenecks.
# Bad - N+1 query
Post.all.each do |post|
puts post.author.name # Query for each author!
end
# => 1 query for posts + N queries for authors# Good - includes eager loads
Post.includes(:author).each do |post|
puts post.author.name
end
# => 2 queries total (posts + authors)
# For nested associations
Post.includes(author: :profile, comments: :user).each do |post|
puts post.author.name
puts post.author.profile.bio
post.comments.each { |c| puts c.user.name }
end# Use includes (eager load) - separate queries, works with all associations
Post.includes(:author).where("authors.name LIKE ?", "%John%").references(:author)
# Use preload - always separate queries, ignores conditions on associations
Post.preload(:author).where(published: true)
# Use joins - single query with INNER JOIN, duplicates parent rows
Post.joins(:comments).where(comments: { approved: true }).distinct
# Use left_outer_joins - includes records without associations
Post.left_outer_joins(:comments).where(comments: { id: nil }) # posts without comments<%# app/views/posts/show.html.erb %>
<% cache @post do %>
<h1><%= @post.title %></h1>
<p><%= @post.body %></p>
<p>By <%= @post.author.name %></p>
<% end %><%# app/views/posts/show.html.erb %>
<% cache @post do %>
<article>
<h1><%= @post.title %></h1>
<% cache ["comments", @post.comments.maximum(:updated_at)] do %>
<section class="comments">
<%= render @post.comments %>
</section>
<% end %>
</article>
<% end %><%# app/views/posts/index.html.erb %>
<%= render partial: "post", collection: @posts, cached: true %># Expensive computation cache
def calculate_user_score(user_id)
Rails.cache.fetch("user_score/#{user_id}", expires_in: 1.hour) do
ExpensiveScoreCalculator.call(user_id)
end
end
# With race condition protection
Rails.cache.fetch("hot_data", race_condition_ttl: 10.seconds) do
fetch_from_slow_api
end# Bad - loads all columns
User.all.map(&:id)
# Good - only load id
User.pluck(:id)
# Multiple columns
User.pluck(:id, :email, :name)
# With conditions
User.where(active: true).pluck(:email)# Bad - loads all into memory
User.all.each do |user|
user.update!(last_seen: Time.now)
end
# Good - process in batches
User.find_each(batch_size: 100) do |user|
user.update!(last_seen: Time.now)
end
# With start point for resuming
User.where("id > ?", last_processed_id).find_each do |user|
process(user)
end# Check query plan in Rails console
puts User.where(email: 'test@example.com').explain
# Detailed analysis
result = ActiveRecord::Base.connection.execute(<<-SQL)
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT * FROM posts WHERE user_id = 1
SQL
puts JSON.pretty_generate(JSON.parse(result.first["QUERY PLAN"]))# config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.alert = true # JavaScript alert
Bullet.bullet_logger = true # Log to bullet.log
Bullet.console = true # Browser console
Bullet.rails_logger = true # Rails log
Bullet.add_footer = true # Add footer to HTML
end# spec/requests/posts_spec.rb
RSpec.describe "Posts", type: :request do
it "does not produce N+1 queries" do
create_list(:post, 5, :with_author)
expect {
get posts_path
}.to make_database_queries(count: 3) # posts + authors + count
end
end# spec/support/matchers/query_count.rb
RSpec::Matchers.define :make_database_queries do |count:|
match do |block|
query_count = 0
subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*|
query_count += 1
end
block.call
query_count == count
ensure
ActiveSupport::Notifications.unsubscribe(subscriber)
end
enddocs
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