ai_misc/skills/hoblin-draper-decorators/SKILL.md
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.
npx skillsauth add madbomber/experiments Draper DecoratorsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides guidance for creating effective Draper decorators in Rails applications.
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:
created_at.strftime("%B %d, %Y"))"#{first_name} #{last_name}")h.content_tag(:span, status, class: css_class))What does NOT belong in decorators:
# 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
delegate_all (Convenient)Delegates all methods to the wrapped object via method_missing. Use for most decorators.
class ProductDecorator < ApplicationDecorator
delegate_all
def formatted_price
h.number_to_currency(price)
end
end
Explicitly declare which methods to delegate. Use for larger apps where control matters.
class ProductDecorator < ApplicationDecorator
delegate :id, :name, :price, :created_at, :persisted?
def formatted_price
h.number_to_currency(price)
end
end
Three equivalent ways to access the model:
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
Use h or helpers to access view helpers:
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
Decorate at the last moment, right before rendering:
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.
Use decorates_association to auto-decorate associations:
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.
Pass extra data to decorators via context:
# 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
# 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
Place specs in spec/decorators/. Draper auto-configures RSpec integration.
# 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
Access helpers via helpers method in tests:
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
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
Split large decorators into context-specific ones:
# Instead of one 500-line UserDecorator, use:
class Users::ProfileDecorator < ApplicationDecorator
# Profile-related presentation
end
class Users::AdminDecorator < ApplicationDecorator
# Admin panel presentation
end
# 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
# 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
# BAD - model references decorator
class Post < ApplicationRecord
def display_title
PostDecorator.new(self).formatted_title
end
end
# GOOD - keep models unaware of decorators
| 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 |
For detailed patterns and examples:
references/patterns.md - Advanced patterns, association decoration, context handlingreferences/testing.md - Comprehensive RSpec testing guidereferences/anti-patterns.md - Detailed anti-patterns with solutionsWorking examples in examples/:
examples/application_decorator.rb - Base decorator templateexamples/model_decorator.rb - Full decorator exampleexamples/decorator_spec.rb - Complete spec templatedata-ai
Postgres performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.
development
Apply SOLID principles when writing, reviewing, or refactoring Ruby code. This skill should be used when designing classes, evaluating architecture, reviewing pull requests, or refactoring existing code. It provides actionable checklists, violation detection patterns, and Ruby-idiomatic refactoring strategies for each of the five SOLID principles.
tools
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
development
Analyze Ruby and Rails code quality with RubyCritic. Identifies code smells, complexity issues, and refactoring opportunities. Provides detailed metrics, scores files A-F, compares branches, and prioritizes high-churn problem areas. Use when analyzing Ruby code quality, reviewing PRs, or identifying technical debt.