37signals-rails-style

Apply 37signals/DHH Rails conventions when writing Ruby on Rails code. Use when building Rails applications, reviewing Rails code, or making architectural decisions. Covers various aspects of Rails application architecture, design and dependencies.

181 stars

Best use case

37signals-rails-style is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Apply 37signals/DHH Rails conventions when writing Ruby on Rails code. Use when building Rails applications, reviewing Rails code, or making architectural decisions. Covers various aspects of Rails application architecture, design and dependencies.

Teams using 37signals-rails-style should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/37signals-rails-style/SKILL.md --create-dirs "https://raw.githubusercontent.com/majiayu000/claude-skill-registry/main/skills/data/37signals-rails-style/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/37signals-rails-style/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How 37signals-rails-style Compares

Feature / Agent37signals-rails-styleStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Apply 37signals/DHH Rails conventions when writing Ruby on Rails code. Use when building Rails applications, reviewing Rails code, or making architectural decisions. Covers various aspects of Rails application architecture, design and dependencies.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# 37signals/DHH Rails Style Guide

## Core Philosophy

- **"Vanilla Rails is plenty."** Maximize what Rails gives you, minimize dependencies, resist abstractions until necessary.
- **Rich domain models** over service objects
- **CRUD controllers** over custom actions
- **Concerns** for horizontal code sharing
- **Records as state** over boolean columns
- **Database-backed everything** (no Redis)
- **Build it yourself** before reaching for gems

---

## Dependencies

### Use
- Rails (edge), turbo-rails, stimulus-rails, importmap-rails, propshaft, solid_queue, solid_cache, solid_cable (database-backed, NO Redis), geared_pagination, bcrypt, rqrcode, redcarpet

### Avoid

| Gem/Pattern            | Why                               |
|------------------------|-----------------------------------|
| `devise`               | Auth is ~150 lines of custom code |
| `pundit`/`cancancan`   | Authorization lives in models     |
| `dry-rb`, `interactor` | Over-engineered                   |
| `view_component`       | ERB partials are fine             |
| `sidekiq`, `redis`     | Use Solid Queue (database-backed) |
| `graphql`              | REST with Turbo is sufficient     |

---

## Routing: Everything is CRUD

Every action maps to a CRUD verb. Create new resources instead of custom actions:

```ruby
# Avoid: Custom actions
resources :cards do
  post :close
end

# Good: State changes as resources
resources :cards do
  resource :closure      # POST to close, DELETE to reopen
  resource :pin          # POST to pin, DELETE to unpin
  resource :watch        # POST to watch, DELETE to unwatch
end
```

Use `scope module:` for namespaced nested resources. Use `resolve` for custom polymorphic URL generation.

---

## Controllers

### Thin Controllers, Rich Models

Controllers orchestrate; business logic lives in models.

```ruby
def create
  @card.close  # All logic in model
  respond_to do |format|
    format.turbo_stream { render_card_replacement }
    format.json { head :no_content }
  end
end
```

### Controller Concerns

Use concerns for shared behavior:
- **Resource scoping**: `CardScoped`, `BoardScoped` - load parent resources via `before_action`
- **Request context**: `CurrentRequest` - populate `Current` with request data
- **Security**: `BlockSearchEngineIndexing`, `RequestForgeryProtection`
- **Turbo helpers**: `TurboFlash` - flash messages via Turbo Stream

### Authorization

Check permissions in controller, define permission logic in model:

```ruby
# Controller
before_action :ensure_permission_to_administer_card, only: [:destroy]

# Model
def can_administer_card?(card)
  admin? || card.creator == self
end
```

---

## Models & Concerns

### Heavy Use of Concerns

Each concern is self-contained with associations, scopes, and methods:

```ruby
class Card < ApplicationRecord
  include Assignable, Closeable, Eventable, Pinnable, Watchable
end
```

### Concern Structure

```ruby
module Card::Closeable
  extend ActiveSupport::Concern

  included do
    has_one :closure, dependent: :destroy
    scope :closed, -> { joins(:closure) }
    scope :open, -> { where.missing(:closure) }
  end

  def closed? = closure.present?

  def close(user: Current.user)
    create_closure!(user: user) unless closed?
  end
end
```

### Default Values via Lambdas

```ruby
belongs_to :account, default: -> { board.account }
belongs_to :creator, class_name: "User", default: -> { Current.user }
```

### Current for Request Context

Use `ActiveSupport::CurrentAttributes` for session, user, identity, account, and request metadata.

### POROs (Plain Old Ruby Objects)

Namespace under parent model: `Event::Description`, `Card::Eventable::SystemCommenter`

Use for:
- **Presentation logic** - formatting for display
- **Complex operations** - multi-step processes
- **View context bundling** - collecting UI state

POROs are model-adjacent, NOT controller-adjacent (that would be a service object).

---

## State as Records, Not Booleans

Create separate records instead of boolean columns:

```ruby
# Separate record gives you: timestamp, who did it, easy scoping
class Closure < ApplicationRecord
  belongs_to :card, touch: true
  belongs_to :user, optional: true
end

card.closure.present?     # Is it closed?
card.closure.user         # Who closed it?
card.closure.created_at   # When?

# Scoping
Card.closed  # joins(:closure)
Card.open    # where.missing(:closure)
```

Examples: `Closure`, `Pin`, `Watch`, `Publication`, `Goldness`

---

## Authentication

Custom passwordless magic link auth (~150 lines). No Devise.

Key components:
- `Authentication` concern with `require_authentication`, `resume_session`, `start_new_session_for`
- `Session` model (belongs_to identity)
- `MagicLink` model with expiration and consumption
- Bearer token authentication for API access

---

## Views & Turbo/Hotwire

- **Turbo Streams** for partial updates (`turbo_stream.replace`, `turbo_stream.before`)
- **Morphing** for complex updates (`method: :morph`)
- **Partials over ViewComponents** - standard ERB partials with caching
- **Stimulus controllers** - single-purpose, small (~50 lines), `static values`/`static classes` for config, `this.dispatch()` for events, `this.#privateMethod()` for private methods

---

## Background Jobs

- **Shallow jobs, rich models** - jobs just call model methods
- **`_later` and `_now` convention** - `mark_as_read_later` queues job, `mark_as_read_now` executes immediately
- **Solid Queue** - database-backed, no Redis
- **Recurring jobs** via `config/recurring.yml`

---

## Testing

- **Request specs** for controllers (not controller specs)
- **Ship tests with features** in the same commit
- Use `change { }`, `as: :turbo_stream`, `as: :json`

---

## What They Avoid

- **No service objects** - use model methods
- **No form objects** (usually) - exception: `Signup` as ActiveModel
- **No decorators/presenters** - use view helpers
- **No GraphQL** - REST with Turbo

---

## Naming Conventions

### Methods
- **Verbs for actions**: `close`, `reopen`, `publish`
- **Predicates for state**: `closed?`, `published?`

### Concerns
Adjectives describing capability: `Closeable`, `Publishable`, `Watchable`, `Searchable`

### Controllers
Nouns matching the resource: `Cards::ClosuresController`, `Boards::PublicationsController`

### Scopes
- **Ordering**: `chronologically`, `reverse_chronologically`, `alphabetically`, `latest`
- **Preloading**: `preloaded` as standard name for eager loading
- **Parameterized**: `indexed_by(index)`, `sorted_by(sort)`

---

## Caching

### HTTP Caching
- `fresh_when etag: [...]` for conditional GET
- Global `etag { "v1" }` in ApplicationController (bump to bust caches)
- Concern-level ETags for timezone, authentication

### Fragment Caching
- `cache card do` in views
- `cached: true` for collection rendering
- `touch: true` on associations for cache invalidation

---

## Database Patterns

- **UUIDs** for primary keys
- **Every model has `account_id`** for multi-tenancy
- **URL-based multi-tenancy**: `/{account_id}/boards/...`
- **No foreign key constraints** - removed for flexibility

---

## CSS Architecture

- **Vanilla CSS** no Sass, PostCSS, or Tailwind.
- **CSS Cascade Layers** `@layer reset, base, components, modules, utilities`
- **OKLCH color system** with CSS variables
- **Modern features** `@starting-style`, `color-mix()`, `:has()`, native nesting, container queries

---

## API Design

- Same controllers, different format via `respond_to`
- Response codes: Create → `201 Created` + Location, Update/Delete → `204 No Content`
- Bearer token authentication

---

## Callbacks

Use sparingly:
- `after_commit :relay_later, on: :create` for async work
- `before_save :set_defaults` for derived data
- Avoid complex chains, avoid synchronous external calls

---

## Summary

1. **Start with vanilla Rails** - Don't add abstractions until you feel the pain
2. **Models are rich** - Business logic lives in models, not services
3. **Controllers are thin** - Just orchestration and response formatting
4. **Everything is CRUD** - New resource over new action
5. **State is records** - Not boolean columns
6. **Concerns are compositions** - Horizontal behavior sharing
7. **Build before buying** - Auth, search, jobs - all custom
8. **Database is king** - No Redis, no Elasticsearch
9. **Test with fixtures** - Deterministic, fast, simple
10. **Ship incrementally** - Many small commits
11. **Tests ship with features** - Not TDD, not afterthought, but together
12. **Refactor toward consistency** - Establish patterns, then update old code
13. **CSS uses the platform** - Native layers, nesting, OKLCH - no preprocessors
14. **Design tokens everywhere** - CSS variables for colors, spacing, typography

The best code is the code you don't write. The second best is the code that's obviously correct.

Related Skills

acc-psr-coding-style-knowledge

181
from majiayu000/claude-skill-registry

PSR-1 and PSR-12 coding standards knowledge base for PHP 8.5 projects. Provides quick reference for basic coding standard and extended coding style with detection patterns, examples, and antipattern identification. Use for code style audits and compliance reviews.

academic-writing-style

181
from majiayu000/claude-skill-registry

Personalized academic writing assistant for university assignments in Chinese and English. Use when users need help writing/revising academic reports, project docs, technical analyses, research reviews, or case studies. Produces natural prose avoiding AI markers. Triggers: academic writing, assignment, report, technical analysis, research review, case study. | 个性化学术写作助手,适用于中英文大学作业。触发词:学术写作、作业、报告、技术分析、研究综述、案例研究、项目文档。

5-styleguide-generation

181
from majiayu000/claude-skill-registry

Fifth step in building instruction context for codebase

tech-blog

159
from majiayu000/claude-skill-registry

Generates comprehensive technical blog posts, offering detailed explanations of system internals, architecture, and implementation, either through source code analysis or document-driven research.

Content & DocumentationClaude

ontopo

159
from majiayu000/claude-skill-registry

An AI agent skill to search for Israeli restaurants, check table availability, view menus, and retrieve booking links via the Ontopo platform, acting as an unofficial interface to its data.

General Utilities

ux

159
from majiayu000/claude-skill-registry

This AI agent skill provides comprehensive guidance for creating professional and insightful User Experience (UX) designs, covering user research, information architecture, interaction design, visual guidance, and usability evaluation. It aims to produce actionable, user-centered solutions that avoid generic AI aesthetics.

UX Design & StrategyClaude

grail-miner

159
from majiayu000/claude-skill-registry

This skill assists in setting up, managing, and optimizing Grail miners on Bittensor Subnet 81, handling tasks like environment configuration, R2 storage, model checkpoint management, and performance tuning.

DevOps & Infrastructure

thor-skills

159
from majiayu000/claude-skill-registry

An entry point and router for AI agents to manage various THOR-related cybersecurity tasks, including running scans, analyzing logs, troubleshooting, and maintenance.

SecurityClaude

vly-money

159
from majiayu000/claude-skill-registry

Generate crypto payment links for supported tokens and networks, manage access to X402 payment-protected content, and provide direct access to the vly.money wallet interface.

Fintech & CryptoClaude

chrome-debug

159
from majiayu000/claude-skill-registry

This skill empowers AI agents to debug web applications and inspect browser behavior using the Chrome DevTools Protocol (CDP), offering both collaborative (headful) and automated (headless) modes.

Coding & DevelopmentClaude

astro

159
from majiayu000/claude-skill-registry

This skill provides essential Astro framework patterns, focusing on server-side rendering (SSR), static site generation (SSG), middleware, and TypeScript best practices. It helps AI agents implement secure authentication, manage API routes, and debug rendering behaviors within Astro projects.

Coding & Development

whisper-transcribe

159
from majiayu000/claude-skill-registry

Transcribes audio and video files to text using OpenAI's Whisper CLI, enhanced with contextual grounding from local markdown files for improved accuracy.

Media Processing