Design and review Rails applications using Vanilla Rails philosophy from 37signals/Basecamp. Emphasizes thin controllers, rich domain models, and avoiding unnecessary service layers. Use when analyzing Rails codebases, reviewing PRs, or refactoring toward simpler architecture. Triggers on "service layer", "service object", "thin controller", "rich model", "vanilla rails", "dhh style", "over-engineering", "unnecessary abstraction".
Analyzes Rails codebases to identify over-engineering and recommends simpler patterns following Vanilla Rails philosophy.
npx claudepluginhub iuhoay/skillsThis skill is limited to using the following tools:
examples/before-after.mdreferences/anti-patterns.mdreferences/patterns/concerns.mdreferences/patterns/delegated-type.mdreferences/patterns/plain-activerecord.mdreferences/patterns/rich-models.mdreferences/patterns/when-to-use-services.mdDesign and review Rails applications using the Vanilla Rails philosophy from 37signals/Basecamp.
This skill is informed by Fizzy - a production Rails application from 37signals.
Key Fizzy patterns:
@board.update!(board_params), @card.comments.create!(comment_params)include Closeable, Golden, Postponable, Watchablehas_one :closure, has_one :goldnessapp/services/ directoryVanilla Rails embraces Rails's built-in patterns and avoids premature abstraction:
Core Philosophy: Thin controllers that directly invoke a rich domain model. No service layers or other artifacts unless genuinely justified.
┌─────────────────────────────────────────┐
│ CONTROLLERS │
│ (Thin - HTTP concerns only) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ MODELS │
│ (Rich - Business logic lives here) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ ACTIVE RECORD / DATABASE │
└─────────────────────────────────────────┘
Core Rule: Don't add layers beyond what Rails provides unless you have a clear, justified reason.
/vanilla-rails:review for Vanilla Rails architecture review/vanilla-rails:analyze to identify over-engineering/vanilla-rails:simplify [goal] to plan refactoring toward Vanilla Rails| Anti-Pattern | Example | Fix |
|---|---|---|
| Fat service | 100-line service with domain logic | Move logic to model |
| Anemic model | Model with only attributes and associations | Add business methods |
| Controller as orchestrator | Controller calling multiple services | Call rich model methods |
| Premature service | Simple CRUD wrapped in service | Use plain Active Record |
| Service explosion | DoSomethingService for every action | Most should be model methods |
See Anti-Patterns Reference for complete list.
Services are justified when:
Fizzy uses plain objects for this:
# Multi-step signup with ActiveModel::Model
class Signup
include ActiveModel::Model
validates :email_address, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :full_name, presence: true
def create_identity
@identity = Identity.find_or_create_by!(email_address: email_address)
@identity.send_magic_link(for: :sign_up)
end
def complete
# Complex account creation with rollback handling
end
end
Fizzy uses ActiveRecord models for stateful operations:
# Stateful import with status tracking
class Account::Import < ApplicationRecord
enum :status, %w[ pending processing completed failed ].index_by(&:itself), default: :pending
def process(start: nil, callback: nil)
processing!
# Import logic with ZIP file handling
mark_completed
rescue => e
mark_as_failed
raise e
end
end
Prefer expanded conditionals over guard clauses (unless returning early at method start for non-trivial bodies).
# Bad - Guard clause
def todos_for_new_group
ids = params.require(:todolist)[:todo_ids]
return [] unless ids
@bucket.recordings.todos.find(ids.split(","))
end
# Good - Expanded conditional
def todos_for_new_group
if ids = params.require(:todolist)[:todo_ids]
@bucket.recordings.todos.find(ids.split(","))
else
[]
end
end
class methodspublic methods (with initialize at top)private methodsOrder methods vertically by invocation order to help readers follow code flow.
Model endpoints as REST operations. Don't add custom actions - introduce new resources instead.
# Bad
resources :cards do
post :close
post :reopen
end
# Good
resources :cards do
resource :closure
end
No newline under visibility modifiers; indent content under them.
class SomeClass
def some_method
# ...
end
private
def some_private_method
# ...
end
end
If a module only has private methods, mark private at top with extra newline but don't indent.
Write shallow job classes that delegate to domain models:
_later suffix for methods that enqueue jobs_now suffix for synchronous methods# Fizzy pattern: _later enqueues, _now does the work
module Event::Relaying
extend ActiveSupport::Concern
included do
after_create_commit :relay_later
end
def relay_later
Event::RelayJob.perform_later(self)
end
def relay_now
# actual implementation
end
end
class Event::RelayJob < ApplicationJob
def perform(event)
event.relay_now
end
end
Only use ! for methods with a counterpart without !. Don't use ! to flag destructive actions.
| Pattern | Use When | Reference |
|---|---|---|
| Plain Active Record | Simple CRUD, no coordination needed | plain-activerecord.md |
| Rich Model API | Complex behavior single model should own | rich-models.md |
| Concern | Shared behavior across models | concerns.md |
| Delegated Type | "Is-a" relationships with shared identity | delegated-type.md |
| Service/Form | Only when genuinely justified | when-to-use-services.md |
Run /vanilla:analyze to detect:
See examples/ directory for before/after comparisons showing the Vanilla Rails approach.
"Vanilla Rails is plenty." - DHH
Most applications don't need layers beyond what Rails provides. Embrace:
ActiveRecord models as the home of business logicResist:
For more depth, read the Vanilla Rails blog post.
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.