Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/microck-dhh-ruby-style/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 70% | 0% |
Write Ruby and Rails code following DHH's philosophy: clarity over cleverness, convention over configuration, developer happiness above all.
index, show, new, create, edit, update, destroyrubyclass 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
Indent private methods one level under private keyword:
rubyprivate def set_message @message = Message.find(params[:id]) end def message_params params.require(:message).permit(:body) end
Models own business logic, authorization, and broadcasting:
rubyclass 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
Use Current for request context, never pass current_user everywhere:
rubyclass Current < ActiveSupport::CurrentAttributes attribute :user, :session end # Usage anywhere in app Current.user.can_administer?(@message)
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
| 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? |
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 }
Rescue specific exceptions, fail fast with bang methods:
rubydef create @message = @room.messages.create_with_attachment!(message_params) @message.broadcast_create rescue ActiveRecord::RecordNotFound render action: :room_not_found end
| 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 |
For comprehensive patterns and examples, see:
references/patterns.md - Complete code patterns with explanationsreferences/resources.md - Links to source material and further reading| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | fail→pass | 14,048 | 10,512 | -25% | 1 | 1 | 0% | 2,663 | 3,704 | +39% | 0 | 0 | — |
case-05 | pass→pass | 15,511 | 13,438 | -13% | 1 | 1 | 0% | 3,184 | 3,857 | +21% | 0 | 0 | — |
case-01 | fail→pass | 18,440 | 15,577 | -16% | 1 | 1 | 0% | 3,642 | 4,739 | +30% | 0 | 0 | — |
case-02 | fail→pass | 14,278 | 14,274 | -0% | 1 | 1 | 0% | 2,324 | 4,066 | +75% | 0 | 0 | — |
case-03 | fail→pass | 10,990 | 7,093 | -35% | 1 | 1 | 0% | 1,940 | 2,651 | +37% | 0 | 0 | — |
case-04 | pass→pass | 12,876 | 11,852 | -8% | 1 | 1 | 0% | 2,626 | 3,918 | +49% | 0 | 0 | — |
case-07 | fail→pass | 9,753 | 8,860 | -9% | 1 | 1 | 0% | 1,884 | 3,208 | +70% | 0 | 0 | — |
case-08 | pass→pass | 12,817 | 8,986 | -30% | 1 | 1 | 0% | 2,361 | 3,092 | +31% | 0 | 0 | — |
case-09 | fail→pass | 14,110 | 9,823 | -30% | 1 | 1 | 0% | 2,182 | 3,085 | +41% | 0 | 0 | — |
case-10 | pass→pass | 10,462 | 6,421 | -39% | 1 | 1 | 0% | 1,827 | 2,514 | +38% | 0 | 0 | — |
case-11 | pass→pass | 14,118 | 5,743 | -59% | 1 | 1 | 0% | 2,503 | 2,514 | +0% | 0 | 0 | — |
case-12 | pass→pass | 7,508 | 4,338 | -42% | 1 | 1 | 0% | 1,435 | 2,215 | +54% | 0 | 0 | — |
case-13 | fail→pass | 6,427 | 2,832 | -56% | 1 | 1 | 0% | 1,313 | 1,901 | +45% | 0 | 0 | — |
case-14 | fail→pass | 15,559 | 8,681 | -44% | 1 | 1 | 0% | 2,849 | 2,919 | +2% | 0 | 0 | — |
case-15 | fail→pass | 12,574 | 4,250 | -66% | 1 | 1 | 0% | 2,053 | 2,098 | +2% | 0 | 0 | — |
case-16 | fail→pass | 5,378 | 3,356 | -38% | 1 | 1 | 0% | 1,055 | 2,019 | +91% | 0 | 0 | — |
case-17 | pass→pass | 9,457 | 4,064 | -57% | 1 | 1 | 0% | 1,598 | 2,091 | +31% | 0 | 0 | — |
case-18 | pass→pass | 7,964 | 3,818 | -52% | 1 | 1 | 0% | 1,401 | 2,008 | +43% | 0 | 0 | — |
case-19 | fail→pass | 11,688 | 4,847 | -59% | 1 | 1 | 0% | 2,265 | 2,303 | +2% | 0 | 0 | — |
case-20 | fail→fail | 13,052 | 7,785 | -40% | 1 | 1 | 0% | 1,942 | 2,632 | +36% | 0 | 0 | — |
case-21 | fail→fail | 5,143 | 4,194 | -18% | 1 | 1 | 0% | 967 | 2,217 | +129% | 0 | 0 | — |
case-22 | fail→pass | 14,661 | 8,815 | -40% | 1 | 1 | 0% | 2,523 | 2,889 | +15% | 0 | 0 | — |
case-23 | pass→pass | 13,245 | 9,161 | -31% | 1 | 1 | 0% | 2,313 | 3,104 | +34% | 0 | 0 | — |
case-24 | fail→pass | 14,617 | 8,097 | -45% | 1 | 1 | 0% | 2,232 | 2,572 | +15% | 0 | 0 | — |
case-25 | pass→pass | 14,115 | 10,199 | -28% | 1 | 1 | 0% | 2,120 | 3,047 | +44% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +52 percentage points is the difference between those two pass rates over the 25 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.