[2.16.0] Expand DHH Rails/Ruby style skills with 37signals patterns

Massively enhanced reference documentation for both dhh-rails-style and
dhh-ruby-style skills by incorporating patterns from Marc Köhlbrugge's
Unofficial 37signals Coding Style Guide.

dhh-rails-style additions:
- controllers.md: Authorization patterns, rate limiting, Sec-Fetch-Site CSRF
- models.md: Validation philosophy, bang methods, Rails 7.1+ patterns
- frontend.md: Turbo morphing, Stimulus controllers, broadcasting patterns
- architecture.md: Multi-tenancy, database patterns, security, Active Storage
- gems.md: Testing philosophy, expanded what-they-avoid section

dhh-ruby-style additions:
- Development philosophy (ship/validate/refine)
- Rails 7.1+ idioms (params.expect, StringInquirer)
- Extraction guidelines (rule of three)

Credit: Marc Köhlbrugge's unofficial-37signals-coding-style-guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kieran Klaassen
2025-12-21 10:12:55 -08:00
parent 56524260f7
commit 932f4ea69d
8 changed files with 1162 additions and 13 deletions

View File

@@ -619,6 +619,137 @@ EXPOSE 80 443
CMD ["./bin/rails", "server", "-b", "0.0.0.0"]
```
## Development Philosophy
### Ship, Validate, Refine
```ruby
# 1. Merge prototype-quality code to test real usage
# 2. Iterate based on real feedback
# 3. Polish what works, remove what doesn't
```
DHH merges features early to validate in production. Perfect code that no one uses is worse than rough code that gets feedback.
### Fix Root Causes
```ruby
# ✅ Prevent race conditions at the source
config.active_job.enqueue_after_transaction_commit = true
# ❌ Bandaid fix with retries
retry_on ActiveRecord::RecordNotFound, wait: 1.second
```
Address underlying issues rather than symptoms.
### Vanilla Rails Over Abstractions
```ruby
# ✅ Direct ActiveRecord
@card.comments.create!(comment_params)
# ❌ Service layer indirection
CreateCommentService.call(@card, comment_params)
```
Use Rails conventions. Only abstract when genuine pain emerges.
## Rails 7.1+ Idioms
### params.expect (PR #120)
```ruby
# ✅ Rails 7.1+ style
def card_params
params.expect(card: [:title, :description, tags: []])
end
# Returns 400 Bad Request if structure invalid
# Old style
def card_params
params.require(:card).permit(:title, :description, tags: [])
end
```
### StringInquirer (PR #425)
```ruby
# ✅ Readable predicates
event.action.inquiry.completed?
event.action.inquiry.pending?
# Usage
case
when event.action.inquiry.completed?
send_notification
when event.action.inquiry.failed?
send_alert
end
# Old style
event.action == "completed"
```
### Positive Naming
```ruby
# ✅ Positive names
scope :active, -> { where(active: true) }
scope :visible, -> { where(visible: true) }
scope :published, -> { where.not(published_at: nil) }
# ❌ Negative names
scope :not_deleted, -> { ... } # Use :active
scope :non_hidden, -> { ... } # Use :visible
scope :is_not_draft, -> { ... } # Use :published
```
## Extraction Guidelines
### Rule of Three
```ruby
# First time: Just do it inline
def process
# inline logic
end
# Second time: Still inline, note the duplication
def process_again
# same logic
end
# Third time: NOW extract
module Processing
def shared_logic
# extracted
end
end
```
Wait for genuine pain before extracting.
### Start in Controller, Extract When Complex
```ruby
# Phase 1: Logic in controller
def index
@cards = @board.cards.where(status: params[:status])
end
# Phase 2: Move to model scope
def index
@cards = @board.cards.by_status(params[:status])
end
# Phase 3: Extract concern if reused
def index
@cards = @board.cards.filtered(params)
end
```
## Anti-Patterns to Avoid
### Don't Add Service Objects for Simple Cases