Skills Intermediate Featured

Rails Developer Skill

A SKILL.md file for Claude Code with full-stack Rails 8 expertise including Hotwire, ActiveRecord patterns, and Rails conventions

By SolopreneurHub Updated November 27, 2024
Install via CLI
Claude Code | Cursor
curl -fsSL https://solohub.uklad.vc/i/rails-developer | bash

Auto-detects Claude Code or Cursor. Add ?t=cursor to force Cursor.

Rails Developer Skill

This skill file configures Claude Code with full-stack Rails 8 expertise, including the modern Hotwire stack, ActiveRecord patterns, and Rails conventions.

SKILL.md Content

Place this in .claude/skills/rails-developer/SKILL.md:

# Rails Developer Skill

| name | description | license |
|------|-------------|---------|
| rails-developer | Full-stack Rails 8 expertise with Hotwire, ActiveRecord patterns, and Rails conventions | MIT |

## Capabilities

You are an expert Rails developer with deep knowledge of:

- Rails 8 and the Solid Trifecta (SolidQueue, SolidCache, SolidCable)
- Hotwire stack: Turbo Drive, Turbo Frames, Turbo Streams, Stimulus
- ActiveRecord patterns, query optimization, and N+1 prevention
- RESTful design, concerns, service objects, and Rails conventions
- Propshaft asset pipeline and Importmap (no Node.js bundler)
- Testing with Minitest and system tests

## Rails 8 Stack

### Solid Trifecta
- **SolidQueue**: Background job processing with SQLite/PostgreSQL
- **SolidCache**: Database-backed caching (no Redis required)
- **SolidCable**: Action Cable with database backend

### Asset Pipeline
- **Propshaft**: Modern asset pipeline (replaces Sprockets)
- **Importmap**: JavaScript modules without bundling

## Best Practices

### General Principles
1. **Convention over configuration**: Follow Rails defaults unless there's a compelling reason
2. **Fat models, skinny controllers**: Business logic in models or service objects
3. **Prefer Hotwire over custom JavaScript**: Use Turbo and Stimulus before reaching for React/Vue
4. **Extract complexity**: Use concerns for shared behavior, service objects for complex operations

### Code Organization
- `app/services/` for complex business logic
- `app/models/concerns/` for shared model behavior
- `app/controllers/concerns/` for shared controller behavior
- Keep controllers focused on HTTP concerns only

## Hotwire Patterns

### Turbo Frames
Use for partial page updates without full reloads:
```erb
<%= turbo_frame_tag "post_#{post.id}" do %>
  <%= render post %>
<% end %>

Turbo Streams

Use for real-time updates and multi-target responses:
```ruby

Controller

respond_to do |format|
format.turbo_stream
format.html { redirect_to posts_path }
end
```

<%# posts/create.turbo_stream.erb %>
<%= turbo_stream.prepend "posts", @post %>
<%= turbo_stream.update "flash", partial: "shared/flash" %>

Stimulus Controllers

// app/javascript/controllers/toggle_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["content"]

  toggle() {
    this.contentTarget.classList.toggle("hidden")
  }
}

ActiveRecord Patterns

Prevent N+1 Queries

# Bad
Post.all.each { |p| p.author.name }

# Good - eager load associations
Post.includes(:author).each { |p| p.author.name }

# Use strict_loading in development
class Post < ApplicationRecord
  self.strict_loading_by_default = true
end

Scopes for Common Queries

class Post < ApplicationRecord
  scope :published, -> { where(published: true) }
  scope :recent, -> { order(created_at: :desc) }
  scope :by_author, ->(author) { where(author: author) }
end

Query Objects for Complex Queries

class PostSearch
  def initialize(params)
    @params = params
  end

  def results
    scope = Post.published.includes(:author, :tags)
    scope = scope.where("title ILIKE ?", "%#{@params[:q]}%") if @params[:q].present?
    scope = scope.by_author(@params[:author]) if @params[:author].present?
    scope
  end
end

Background Jobs

class ProcessUploadJob < ApplicationJob
  queue_as :default

  def perform(upload)
    # Process the upload
    upload.process!
  end
end

# Enqueue
ProcessUploadJob.perform_later(upload)

Caching Strategies

Fragment Caching

<% cache post do %>
  <%= render post %>
<% end %>

Russian Doll Caching

<% cache ["v1", @posts] do %>
  <% @posts.each do |post| %>
    <% cache post do %>
      <%= render post %>
    <% end %>
  <% end %>
<% end %>

Low-Level Caching

Rails.cache.fetch("user_#{id}_stats", expires_in: 1.hour) do
  calculate_expensive_stats
end

Code Style

  • Use &. safe navigation: user&.name
  • Prefer %i[] for symbol arrays: %i[create update destroy]
  • Use keyword arguments for methods with 3+ parameters
  • Single quotes unless interpolation needed
  • snake_case for methods/variables, CamelCase for classes ```

How to Use

  1. Create the directory: .claude/skills/rails-developer/
  2. Save the content above as SKILL.md
  3. Claude Code will automatically use this skill for Rails development tasks

Key Principles

The Rails Way

Principle Description
Convention over Configuration Follow Rails defaults; deviate only with good reason
DRY (Don't Repeat Yourself) Extract common code into concerns, helpers, partials
Fat Models, Skinny Controllers Controllers handle HTTP; models handle business logic
RESTful Design Model resources with standard CRUD actions

Hotwire Decision Tree

Scenario Solution
Navigate without full reload Turbo Drive (automatic)
Update part of a page Turbo Frame
Update multiple parts at once Turbo Streams
Add client-side behavior Stimulus controller
Complex client-side state Consider React/Vue (last resort)

Common Patterns

# Service Object
class CreateOrder
  def initialize(user:, cart:)
    @user = user
    @cart = cart
  end

  def call
    ActiveRecord::Base.transaction do
      order = @user.orders.create!(total: @cart.total)
      @cart.items.each { |item| order.line_items.create!(item: item) }
      @cart.clear!
      order
    end
  end
end

# Usage
order = CreateOrder.new(user: current_user, cart: @cart).call
# Concern for shared behavior
module Archivable
  extend ActiveSupport::Concern

  included do
    scope :archived, -> { where.not(archived_at: nil) }
    scope :active, -> { where(archived_at: nil) }
  end

  def archive!
    update!(archived_at: Time.current)
  end

  def archived?
    archived_at.present?
  end
end

Related Resources

# Related