Major restructure of the compounding-engineering plugin: ## Agents (24 total, now categorized) - review/ (10): architecture-strategist, code-simplicity-reviewer, data-integrity-guardian, dhh-rails-reviewer, kieran-rails-reviewer, kieran-python-reviewer, kieran-typescript-reviewer, pattern-recognition-specialist, performance-oracle, security-sentinel - research/ (4): best-practices-researcher, framework-docs-researcher, git-history-analyzer, repo-research-analyst - design/ (3): design-implementation-reviewer, design-iterator, figma-design-sync - workflow/ (6): bug-reproduction-validator, every-style-editor, feedback-codifier, lint, pr-comment-resolver, spec-flow-analyzer - docs/ (1): ankane-readme-writer ## Commands (15 total) - Moved workflow commands to commands/workflows/ subdirectory - Added: changelog, create-agent-skill, heal-skill, plan_review, prime, reproduce-bug, resolve_parallel, resolve_pr_parallel ## Skills (11 total) - Added: andrew-kane-gem-writer, codify-docs, create-agent-skills, dhh-ruby-style, dspy-ruby, every-style-editor, file-todos, frontend-design, git-worktree, skill-creator - Kept: gemini-imagegen 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
5.6 KiB
Markdown
202 lines
5.6 KiB
Markdown
---
|
|
name: dhh-ruby-style
|
|
description: Write Ruby and Rails code in DHH's distinctive 37signals style. Use this skill when writing Ruby code, Rails applications, creating models, controllers, or any Ruby file. Triggers on Ruby/Rails code generation, refactoring requests, code review, or when the user mentions DHH, 37signals, Basecamp, HEY, or Campfire style. Embodies REST purity, fat models, thin controllers, Current attributes, Hotwire patterns, and the "clarity over cleverness" philosophy.
|
|
---
|
|
|
|
# DHH Ruby/Rails Style Guide
|
|
|
|
Write Ruby and Rails code following DHH's philosophy: **clarity over cleverness**, **convention over configuration**, **developer happiness** above all.
|
|
|
|
## Quick Reference
|
|
|
|
### Controller Actions
|
|
- **Only 7 REST actions**: `index`, `show`, `new`, `create`, `edit`, `update`, `destroy`
|
|
- **New behavior?** Create a new controller, not a custom action
|
|
- **Action length**: 1-5 lines maximum
|
|
- **Empty actions are fine**: Let Rails convention handle rendering
|
|
|
|
```ruby
|
|
class MessagesController < ApplicationController
|
|
before_action :set_message, only: %i[ show edit update destroy ]
|
|
|
|
def index
|
|
@messages = @room.messages.with_creator.last_page
|
|
fresh_when @messages
|
|
end
|
|
|
|
def show
|
|
end
|
|
|
|
def create
|
|
@message = @room.messages.create_with_attachment!(message_params)
|
|
@message.broadcast_create
|
|
end
|
|
|
|
private
|
|
def set_message
|
|
@message = @room.messages.find(params[:id])
|
|
end
|
|
|
|
def message_params
|
|
params.require(:message).permit(:body, :attachment)
|
|
end
|
|
end
|
|
```
|
|
|
|
### Private Method Indentation
|
|
Indent private methods one level under `private` keyword:
|
|
|
|
```ruby
|
|
private
|
|
def set_message
|
|
@message = Message.find(params[:id])
|
|
end
|
|
|
|
def message_params
|
|
params.require(:message).permit(:body)
|
|
end
|
|
```
|
|
|
|
### Model Design (Fat Models)
|
|
Models own business logic, authorization, and broadcasting:
|
|
|
|
```ruby
|
|
class Message < ApplicationRecord
|
|
belongs_to :room
|
|
belongs_to :creator, class_name: "User"
|
|
has_many :mentions
|
|
|
|
scope :with_creator, -> { includes(:creator) }
|
|
scope :page_before, ->(cursor) { where("id < ?", cursor.id).order(id: :desc).limit(50) }
|
|
|
|
def broadcast_create
|
|
broadcast_append_to room, :messages, target: "messages"
|
|
end
|
|
|
|
def mentionees
|
|
mentions.includes(:user).map(&:user)
|
|
end
|
|
end
|
|
|
|
class User < ApplicationRecord
|
|
def can_administer?(message)
|
|
message.creator == self || admin?
|
|
end
|
|
end
|
|
```
|
|
|
|
### Current Attributes
|
|
Use `Current` for request context, never pass `current_user` everywhere:
|
|
|
|
```ruby
|
|
class Current < ActiveSupport::CurrentAttributes
|
|
attribute :user, :session
|
|
end
|
|
|
|
# Usage anywhere in app
|
|
Current.user.can_administer?(@message)
|
|
```
|
|
|
|
### Ruby Syntax Preferences
|
|
|
|
```ruby
|
|
# Symbol arrays with spaces inside brackets
|
|
before_action :set_message, only: %i[ show edit update destroy ]
|
|
|
|
# Modern hash syntax exclusively
|
|
params.require(:message).permit(:body, :attachment)
|
|
|
|
# Single-line blocks with braces
|
|
users.each { |user| user.notify }
|
|
|
|
# Ternaries for simple conditionals
|
|
@room.direct? ? @room.users : @message.mentionees
|
|
|
|
# Bang methods for fail-fast
|
|
@message = Message.create!(params)
|
|
@message.update!(message_params)
|
|
|
|
# Predicate methods with question marks
|
|
@room.direct?
|
|
user.can_administer?(@message)
|
|
@messages.any?
|
|
|
|
# Expression-less case for cleaner conditionals
|
|
case
|
|
when params[:before].present?
|
|
@room.messages.page_before(params[:before])
|
|
when params[:after].present?
|
|
@room.messages.page_after(params[:after])
|
|
else
|
|
@room.messages.last_page
|
|
end
|
|
```
|
|
|
|
### Naming Conventions
|
|
|
|
| Element | Convention | Example |
|
|
|---------|------------|---------|
|
|
| Setter methods | `set_` prefix | `set_message`, `set_room` |
|
|
| Parameter methods | `{model}_params` | `message_params` |
|
|
| Association names | Semantic, not generic | `creator` not `user` |
|
|
| Scopes | Chainable, descriptive | `with_creator`, `page_before` |
|
|
| Predicates | End with `?` | `direct?`, `can_administer?` |
|
|
|
|
### Hotwire/Turbo Patterns
|
|
Broadcasting is model responsibility:
|
|
|
|
```ruby
|
|
# In model
|
|
def broadcast_create
|
|
broadcast_append_to room, :messages, target: "messages"
|
|
end
|
|
|
|
# In controller
|
|
@message.broadcast_replace_to @room, :messages,
|
|
target: [ @message, :presentation ],
|
|
partial: "messages/presentation",
|
|
attributes: { maintain_scroll: true }
|
|
```
|
|
|
|
### Error Handling
|
|
Rescue specific exceptions, fail fast with bang methods:
|
|
|
|
```ruby
|
|
def create
|
|
@message = @room.messages.create_with_attachment!(message_params)
|
|
@message.broadcast_create
|
|
rescue ActiveRecord::RecordNotFound
|
|
render action: :room_not_found
|
|
end
|
|
```
|
|
|
|
### Architecture Preferences
|
|
|
|
| Traditional | DHH Way |
|
|
|-------------|---------|
|
|
| PostgreSQL | SQLite (for single-tenant) |
|
|
| Redis + Sidekiq | Solid Queue |
|
|
| Redis cache | Solid Cache |
|
|
| Kubernetes | Single Docker container |
|
|
| Service objects | Fat models |
|
|
| Policy objects (Pundit) | Authorization on User model |
|
|
| FactoryBot | Fixtures |
|
|
|
|
## Detailed References
|
|
|
|
For comprehensive patterns and examples, see:
|
|
- `references/patterns.md` - Complete code patterns with explanations
|
|
- `references/resources.md` - Links to source material and further reading
|
|
|
|
## Philosophy Summary
|
|
|
|
1. **REST purity**: 7 actions only; new controllers for variations
|
|
2. **Fat models**: Authorization, broadcasting, business logic in models
|
|
3. **Thin controllers**: 1-5 line actions; extract complexity
|
|
4. **Convention over configuration**: Empty methods, implicit rendering
|
|
5. **Minimal abstractions**: No service objects for simple cases
|
|
6. **Current attributes**: Thread-local request context everywhere
|
|
7. **Hotwire-first**: Model-level broadcasting, Turbo Streams, Stimulus
|
|
8. **Readable code**: Semantic naming, small methods, no comments needed
|
|
9. **Pragmatic testing**: System tests over unit tests, real integrations
|