draper-decorators
This skill should be used when the user asks to "create a decorator", "write a decorator", "move logic into decorator", "clean logic out of the view", "isn't it decorator logic", "test a decorator", or mentions Draper, keeping views clean, or representation logic in decorators. Should also be used when editing *_decorator.rb files, working in app/decorators/ directory, questioning where formatting methods belong (models vs decorators vs views), or discussing methods like full_name, formatted_*, display_* that don't belong in models. Provides guidance on Draper gem best practices for Rails applications.
What this skill does
# Draper Decorators for Rails
This skill provides guidance for creating effective Draper decorators in Rails applications.
## Philosophy
Decorators implement separation of concerns between **business logic** (models) and **presentation logic** (views). A decorator wraps a model to add view-specific methods without polluting the model.
**What belongs in decorators:**
- Date/time formatting (`created_at.strftime("%B %d, %Y")`)
- String concatenation (`"#{first_name} #{last_name}"`)
- HTML generation (`h.content_tag(:span, status, class: css_class)`)
- Conditional rendering based on state
- Number formatting (currency, percentages)
- CSS class generation based on object state
**What does NOT belong in decorators:**
- Business logic (validations, calculations, state changes)
- Database queries (use includes in controllers)
- Anything not directly related to presentation
## Basic Structure
```ruby
# app/decorators/user_decorator.rb
class UserDecorator < ApplicationDecorator
delegate_all
def full_name
"#{first_name} #{last_name}"
end
def formatted_created_at
created_at.strftime("%B %d, %Y")
end
def status_badge
css_class = active? ? "badge-success" : "badge-secondary"
h.content_tag(:span, status, class: "badge #{css_class}")
end
end
```
## Delegation Strategies
### Option 1: `delegate_all` (Convenient)
Delegates all methods to the wrapped object via `method_missing`. Use for most decorators.
```ruby
class ProductDecorator < ApplicationDecorator
delegate_all
def formatted_price
h.number_to_currency(price)
end
end
```
### Option 2: Explicit Delegation (Strict)
Explicitly declare which methods to delegate. Use for larger apps where control matters.
```ruby
class ProductDecorator < ApplicationDecorator
delegate :id, :name, :price, :created_at, :persisted?
def formatted_price
h.number_to_currency(price)
end
end
```
## Accessing the Wrapped Object
Three equivalent ways to access the model:
```ruby
class ArticleDecorator < ApplicationDecorator
delegate_all
def display_title
object.title.upcase # via 'object'
model.title.upcase # via 'model' (alias)
article.title.upcase # via model name (auto-generated)
end
end
```
## Accessing Rails Helpers
Use `h` or `helpers` to access view helpers:
```ruby
class PostDecorator < ApplicationDecorator
delegate_all
def formatted_body
h.simple_format(body)
end
def edit_link
h.link_to("Edit", h.edit_post_path(object), class: "btn")
end
def publication_date
h.l(published_at, format: :long) # l is localize alias
end
end
```
## Decorating in Controllers
Decorate **at the last moment**, right before rendering:
```ruby
class PostsController < ApplicationController
def show
@post = Post.find(params[:id]).decorate
end
def index
@posts = Post.includes(:author).all.decorate
end
end
```
**Critical:** Always use `includes` BEFORE decorating to avoid N+1 queries.
## Association Decoration
Use `decorates_association` to auto-decorate associations:
```ruby
class PostDecorator < ApplicationDecorator
delegate_all
decorates_association :author
decorates_association :comments
decorates_association :recent_comments, scope: :recent
end
```
In views, `@post.author` returns `AuthorDecorator`, not `Author`.
## Context Passing
Pass extra data to decorators via context:
```ruby
# Controller
@product = Product.find(params[:id]).decorate(context: { current_user: })
# Decorator
class ProductDecorator < ApplicationDecorator
delegate_all
def admin_price_info
return unless context[:current_user]&.admin?
"Cost: #{h.number_to_currency(cost)} | Margin: #{margin}%"
end
end
```
## Collection Decoration
```ruby
# Auto-infers decorator from model
@products = Product.all.decorate
# Explicit decorator
@products = ProductDecorator.decorate_collection(Product.all)
# With pagination (use custom collection decorator)
class PaginatingDecorator < Draper::CollectionDecorator
delegate :current_page, :total_pages, :limit_value
end
class ProductDecorator < ApplicationDecorator
def self.collection_decorator_class
PaginatingDecorator
end
end
```
## Testing Decorators
Place specs in `spec/decorators/`. Draper auto-configures RSpec integration.
### Basic Pattern
```ruby
# spec/decorators/user_decorator_spec.rb
require 'rails_helper'
RSpec.describe UserDecorator do
subject(:decorator) { described_class.new(user) }
let(:user) { build_stubbed(:user, first_name: "John", last_name: "Doe") }
describe "#full_name" do
subject(:full_name) { decorator.full_name }
it "combines first and last name" do
expect(full_name).to eq("John Doe")
end
end
describe "#formatted_created_at" do
subject(:formatted_date) { decorator.formatted_created_at }
let(:user) { build_stubbed(:user, created_at: Time.zone.parse("2024-01-15")) }
it "formats date in long format" do
expect(formatted_date).to eq("January 15, 2024")
end
end
end
```
### Testing with Helpers
Access helpers via `helpers` method in tests:
```ruby
RSpec.describe PostDecorator do
subject(:decorator) { described_class.new(post) }
let(:post) { create(:post) }
it "generates correct path" do
expect(decorator.edit_link).to include(helpers.edit_post_path(post))
end
end
```
### Testing HTML Output with Capybara
```ruby
RSpec.describe StatusDecorator do
subject(:decorator) { described_class.new(order) }
describe "#status_badge" do
subject(:badge) { decorator.status_badge }
context "when completed" do
let(:order) { build_stubbed(:order, :completed) }
it "renders success badge" do
markup = Capybara.string(badge)
expect(markup).to have_css("span.badge-success", text: "Completed")
end
end
end
end
```
## Common Anti-Patterns
### Fat Decorator
Split large decorators into context-specific ones:
```ruby
# Instead of one 500-line UserDecorator, use:
class Users::ProfileDecorator < ApplicationDecorator
# Profile-related presentation
end
class Users::AdminDecorator < ApplicationDecorator
# Admin panel presentation
end
```
### N+1 Queries
```ruby
# BAD - triggers N+1
@posts = Post.all.decorate
# In decorator: author.name triggers query per post
# GOOD - eager load first
@posts = Post.includes(:author).all.decorate
```
### Decorating Too Early
```ruby
# BAD - decorated objects in business logic
def publish(decorated_post)
decorated_post.update(published: true)
end
# GOOD - use models for business logic
def publish(post)
post.update(published: true)
end
# Decorate only in controller before render
```
### Using Decorators in Models
```ruby
# BAD - model references decorator
class Post < ApplicationRecord
def display_title
PostDecorator.new(self).formatted_title
end
end
# GOOD - keep models unaware of decorators
```
## Quick Reference
| Method | Purpose |
|--------|---------|
| `object` / `model` | Access wrapped object |
| `h` / `helpers` | Access view helpers |
| `context` | Access passed context hash |
| `delegate_all` | Delegate all methods to object |
| `decorates_association` | Auto-decorate associations |
| `decorate` | Decorate single object |
| `decorate_collection` | Decorate collection |
## Additional Resources
### Reference Files
For detailed patterns and examples:
- **`references/patterns.md`** - Advanced patterns, association decoration, context handling
- **`references/testing.md`** - Comprehensive RSpec testing guide
- **`references/anti-patterns.md`** - Detailed anti-patterns with solutions
### Example Files
Working examples in `examples/`:
- **`examples/application_decorator.rb`** - Base decorator template
- **`examples/model_decorator.rb`** - Full decorator example
- **`examples/decorator_spec.rb`** - Complete spec template
Related in Code Review
gstack
IncludedFast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff before/after, take annotated screenshots, test responsive layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)
startup-due-diligence
IncludedLegal due diligence review for seed-stage and Series A startups (US, Delaware C-Corp focus). Supports both investor and founder perspectives. Capabilities include: (1) Interactive document review and issue spotting; (2) Document request list generation; (3) Cap table and SAFE/convertible note analysis; (4) Red flag identification with severity ratings; (5) Diligence report generation. TRIGGERS: due diligence, DD, startup investment, cap table review, Series A, seed round, investor diligence, legal review startup, SAFE analysis, convertible note, 409A, founder vesting.
interview-master
IncludedThis skill should be used when the user asks to "generate interview questions", "prepare for interview", "optimize resume", "conduct mock interview", "analyze git commits for resume", "generate resume from code", "review my resume", or mentions interview preparation, career assistance, or extracting project experience from git history. Provides comprehensive interview and career development guidance for both job seekers and interviewers.
fix-issue
IncludedFixes GitHub issues using parallel analysis agents for root cause investigation, code exploration, and regression detection. Reads issue context from gh CLI, searches codebase and memory for related patterns, generates a fix with tests, and links the resolution back to the issue via PR. Includes prevention analysis to avoid recurrence. Use when debugging errors, resolving regressions, fixing bugs, or triaging issues.
sf-apex
IncludedGenerates and reviews Salesforce Apex code with 150-point scoring. TRIGGER when: user writes, reviews, or fixes Apex classes, triggers, test classes, batch/queueable/schedulable jobs, or touches .cls/.trigger files. DO NOT TRIGGER when: LWC JavaScript (use sf-lwc), Flow XML (use sf-flow), SOQL-only queries (use sf-soql), or non-Salesforce code.
swift-development
IncludedComprehensive Swift development for building, testing, and deploying iOS/macOS applications. Use when Claude needs to: (1) Build Swift packages or Xcode projects from command line, (2) Run tests with XCTest or Swift Testing framework, (3) Manage iOS simulators with simctl, (4) Handle code signing, provisioning profiles, and app distribution, (5) Format or lint Swift code with SwiftFormat/SwiftLint, (6) Work with Swift Package Manager (SPM), (7) Implement Swift 6 concurrency patterns (async/await, actors, Sendable), (8) Create SwiftUI views with MVVM architecture, (9) Set up Core Data or SwiftData persistence, or any other Swift/iOS/macOS development tasks.