搜索

PythonReactSEOPDF
GitHub Skill

dhh-rails-style

Write Ruby and Rails code in DHH's 37signals style. Use when writing Rails code, creating models, controllers, or any Ruby file. Embodies REST purity, fat models, thin controllers, Current attributes, Hotwire patterns, and "clarity over cleverness."

newstler☆ 18 仓库 Stars0 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

DHH Rails Style Guide

Apply 37signals/DHH conventions to Ruby and Rails code.

Core Philosophy

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

Vanilla Rails is plenty:

  • Rich domain models over service objects
  • CRUD controllers over custom actions
  • Concerns for horizontal code sharing
  • Records as state instead of boolean columns
  • Database-backed everything (no Redis)
  • Build solutions before reaching for gems

What We Deliberately Avoid

Avoid Use Instead
devise Custom ~150-line auth
pundit/cancancan Simple role checks in models
sidekiq Solid Queue (database-backed)
redis Database for everything
view_component Partials
GraphQL REST with Turbo
React/Vue Hotwire + Stimulus
RSpec Minitest
FactoryBot Fixtures

Naming Conventions

Methods
# Verbs for actions
card.close
card.gild
board.publish

# Predicates return boolean
card.closed?
card.golden?
user.admin?

# Avoid set_ methods
# ❌ card.set_status("closed")
# ✅ card.close
Concerns

Name as adjectives describing capability:

module Closeable
  extend ActiveSupport::Concern
  # ...
end

module Publishable; end
module Watchable; end
module Searchable; end
Scopes
# Ordering
scope :chronologically, -> { order(created_at: :asc) }
scope :reverse_chronologically, -> { order(created_at: :desc) }
scope :alphabetically, -> { order(name: :asc) }
scope :latest, -> { order(created_at: :desc).limit(1) }

# Eager loading
scope :preloaded, -> { includes(:author, :comments) }

# Parameterized
scope :sorted_by, ->(column) { order(column) }
scope :created_after, ->(date) { where("created_at > ?", date) }
Controllers

Nouns matching resources:

# ❌ Bad: Custom actions
class CardsController
  def close; end
  def reopen; end
end

# ✅ Good: Nested resource
class Cards::ClosuresController
  def create; end   # POST /cards/:id/closure
  def destroy; end  # DELETE /cards/:id/closure
end

REST Mapping

Transform custom actions into resources:

POST /cards/:id/close    → POST /cards/:id/closure
DELETE /cards/:id/close  → DELETE /cards/:id/closure
POST /cards/:id/archive  → POST /cards/:id/archival
POST /cards/:id/publish  → POST /cards/:id/publication

Controller Patterns

class Cards::ClosuresController < ApplicationController
  before_action :set_card

  def create
    @card.close(by: Current.user)
    redirect_to @card
  end

  def destroy
    @card.reopen(by: Current.user)
    redirect_to @card
  end

  private

  def set_card
    @card = Current.user.cards.find(params[:card_id])
  end
end

Model Patterns

State as Records
# ❌ Bad: Boolean column
class Card < ApplicationRecord
  # closed: boolean
  def close
    update!(closed: true)
  end
end

# ✅ Good: State record
class Card < ApplicationRecord
  has_one :closure, dependent: :destroy

  def close(by: Current.user)
    create_closure!(closed_by: by)
  end

  def closed?
    closure.present?
  end
end

class Closure < ApplicationRecord
  belongs_to :card
  belongs_to :closed_by, class_name: "User"
end
Concerns
# app/models/concerns/closeable.rb
module Closeable
  extend ActiveSupport::Concern

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

  def close(by: Current.user)
    create_closure!(closed_by: by)
  end

  def reopen
    closure&.destroy
  end

  def closed?
    closure.present?
  end
end

Current Attributes

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user, :session, :request_id

  def user=(user)
    super
    Time.zone = user&.time_zone || "UTC"
  end
end

# Usage anywhere
Current.user
Current.session

Frontend Patterns

Turbo Frames
<%= turbo_frame_tag dom_id(@card) do %>
  <%= render @card %>
<% end %>
Turbo Streams
<%# app/views/cards/create.turbo_stream.erb %>
<%= turbo_stream.prepend "cards", @card %>
<%= turbo_stream.update "flash", partial: "shared/flash" %>
Stimulus
// Small, focused controllers
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["menu"]

  toggle() {
    this.menuTarget.classList.toggle("hidden")
  }
}

Testing

# Minitest + fixtures
class CardTest < ActiveSupport::TestCase
  test "can be closed" do
    card = cards(:open)
    card.close(by: users(:admin))
    assert card.closed?
  end
end

Success Criteria

Code follows DHH style when:

  • Controllers map to CRUD verbs on resources
  • Models use concerns for horizontal behavior
  • State tracked via records, not booleans
  • No service objects or unnecessary abstractions
  • Database-backed solutions (no Redis)
  • Tests use Minitest with fixtures
  • Turbo/Stimulus for interactivity
  • No npm/yarn dependencies

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-03-11
索引更新时间
2026-03-11
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!