dhh-ruby-style
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDHH Ruby/Rails Style Guide
DHH Ruby/Rails 风格指南
Write Ruby and Rails code following DHH's philosophy: clarity over cleverness, convention over configuration, developer happiness above all.
遵循DHH的理念编写Ruby和Rails代码:清晰胜于精巧、约定优于配置、开发者幸福感至上。
Quick Reference
快速参考
Controller Actions
控制器动作
- Only 7 REST actions: ,
index,show,new,create,edit,updatedestroy - New behavior? Create a new controller, not a custom action
- Action length: 1-5 lines maximum
- Empty actions are fine: Let Rails convention handle rendering
ruby
class 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- 仅使用7个REST动作:,
index,show,new,create,edit,updatedestroy - 需要新行为? 创建新控制器,而非自定义动作
- 动作长度:最多1-5行
- 空动作是允许的:让Rails的约定来处理渲染
ruby
class 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
endPrivate Method Indentation
私有方法缩进
Indent private methods one level under keyword:
privateruby
private
def set_message
@message = Message.find(params[:id])
end
def message_params
params.require(:message).permit(:body)
end私有方法在关键字下缩进一级:
privateruby
private
def set_message
@message = Message.find(params[:id])
end
def message_params
params.require(:message).permit(:body)
endModel Design (Fat Models)
模型设计(胖模型)
Models own business logic, authorization, and broadcasting:
ruby
class 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模型负责业务逻辑、授权和广播:
ruby
class 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
endCurrent Attributes
Current 属性
Use for request context, never pass everywhere:
Currentcurrent_userruby
class Current < ActiveSupport::CurrentAttributes
attribute :user, :session
end使用存储请求上下文,不要到处传递:
Currentcurrent_userruby
class Current < ActiveSupport::CurrentAttributes
attribute :user, :session
endUsage anywhere in app
在应用的任何地方使用
Current.user.can_administer?(@message)
undefinedCurrent.user.can_administer?(@message)
undefinedRuby Syntax Preferences
Ruby 语法偏好
ruby
undefinedruby
undefinedSymbol arrays with spaces inside brackets
符号数组在括号内添加空格
before_action :set_message, only: %i[ show edit update destroy ]
before_action :set_message, only: %i[ show edit update destroy ]
Modern hash syntax exclusively
仅使用现代哈希语法
params.require(:message).permit(:body, :attachment)
params.require(:message).permit(:body, :attachment)
Single-line blocks with braces
单行块使用大括号
users.each { |user| user.notify }
users.each { |user| user.notify }
Ternaries for simple conditionals
简单条件使用三元运算符
@room.direct? ? @room.users : @message.mentionees
@room.direct? ? @room.users : @message.mentionees
Bang methods for fail-fast
使用 bang 方法快速失败
@message = Message.create!(params)
@message.update!(message_params)
@message = Message.create!(params)
@message.update!(message_params)
Predicate methods with question marks
谓词方法以问号结尾
@room.direct?
user.can_administer?(@message)
@messages.any?
@room.direct?
user.can_administer?(@message)
@messages.any?
Expression-less case for cleaner conditionals
无表达式的case语句让条件更简洁
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
undefinedcase
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
undefinedNaming Conventions
命名约定
| Element | Convention | Example |
|---|---|---|
| Setter methods | | |
| Parameter methods | | |
| Association names | Semantic, not generic | |
| Scopes | Chainable, descriptive | |
| Predicates | End with | |
| 元素 | 约定 | 示例 |
|---|---|---|
| 设置方法 | 以 | |
| 参数方法 | | |
| 关联名称 | 语义化而非通用 | |
| 作用域 | 可链式调用、描述性强 | |
| 谓词方法 | 以 | |
Hotwire/Turbo Patterns
Hotwire/Turbo 模式
Broadcasting is model responsibility:
ruby
undefined广播是模型的职责:
ruby
undefinedIn model
在模型中
def broadcast_create
broadcast_append_to room, :messages, target: "messages"
end
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 }
undefined@message.broadcast_replace_to @room, :messages,
target: [ @message, :presentation ],
partial: "messages/presentation",
attributes: { maintain_scroll: true }
undefinedError Handling
错误处理
Rescue specific exceptions, fail fast with bang methods:
ruby
def create
@message = @room.messages.create_with_attachment!(message_params)
@message.broadcast_create
rescue ActiveRecord::RecordNotFound
render action: :room_not_found
end捕获特定异常,使用bang方法快速失败:
ruby
def create
@message = @room.messages.create_with_attachment!(message_params)
@message.broadcast_create
rescue ActiveRecord::RecordNotFound
render action: :room_not_found
endArchitecture Preferences
架构偏好
| 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 |
| 传统方案 | DHH 方案 |
|---|---|
| PostgreSQL | SQLite(单租户场景) |
| Redis + Sidekiq | Solid Queue |
| Redis 缓存 | Solid Cache |
| Kubernetes | 单个Docker容器 |
| 服务对象 | 胖模型 |
| 策略对象(Pundit) | 在User模型中处理授权 |
| FactoryBot | 测试夹具(Fixtures) |
Detailed References
详细参考
For comprehensive patterns and examples, see:
- - Complete code patterns with explanations
references/patterns.md - - Links to source material and further reading
references/resources.md
如需完整的模式和示例,请查看:
- - 带解释的完整代码模式
references/patterns.md - - 源材料链接和扩展阅读
references/resources.md
Philosophy Summary
理念总结
- REST purity: 7 actions only; new controllers for variations
- Fat models: Authorization, broadcasting, business logic in models
- Thin controllers: 1-5 line actions; extract complexity
- Convention over configuration: Empty methods, implicit rendering
- Minimal abstractions: No service objects for simple cases
- Current attributes: Thread-local request context everywhere
- Hotwire-first: Model-level broadcasting, Turbo Streams, Stimulus
- Readable code: Semantic naming, small methods, no comments needed
- Pragmatic testing: System tests over unit tests, real integrations
- 纯粹REST:仅使用7个动作;为变体创建新控制器
- 胖模型:授权、广播、业务逻辑都放在模型中
- 瘦控制器:动作1-5行;提取复杂逻辑
- 约定优于配置:空方法、隐式渲染
- 最小化抽象:简单场景不使用服务对象
- Current 属性:线程本地的请求上下文可全局使用
- 优先使用Hotwire:模型级广播、Turbo Streams、Stimulus
- 可读代码:语义化命名、小方法、无需注释
- 务实测试:优先系统测试而非单元测试,真实集成测试