rails-conventions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRails Conventions
Rails 编码规范
Opinionated Rails patterns for clean, maintainable code.
一套用于编写整洁、可维护代码的Rails专属编码模式。
Core Philosophy
核心理念
-
Duplication > Complexity: Simple duplicated code beats complex DRY abstractions
- "I'd rather have four simple controllers than three complex ones"
-
Testability = Quality: If it's hard to test, structure needs refactoring
-
Adding controllers is never bad. Making controllers complex IS bad.
-
重复代码 > 复杂实现:简单的重复代码优于复杂的DRY抽象
- "我宁愿有四个简单的控制器,也不要三个复杂的控制器"
-
可测试性 = 代码质量:如果代码难以测试,说明结构需要重构
-
新增控制器绝非坏事,但让控制器变得复杂才是问题。
Turbo Streams
Turbo Streams
Simple turbo streams MUST be inline in controllers:
ruby
undefined简单的Turbo Streams必须内联在控制器中:
ruby
undefinedFAIL: Separate .turbo_stream.erb files for simple operations
错误示例:为简单操作单独创建.turbo_stream.erb文件
render "posts/update"
render "posts/update"
PASS: Inline array
正确示例:使用内联数组
render turbo_stream: [
turbo_stream.replace("post_#{@post.id}", partial: "posts/post", locals: { post: @post }),
turbo_stream.remove("flash")
]
undefinedrender turbo_stream: [
turbo_stream.replace("post_#{@post.id}", partial: "posts/post", locals: { post: @post }),
turbo_stream.remove("flash")
]
undefinedController & Concerns
控制器与Concern
Business logic belongs in models or concerns, not controllers.
ruby
undefined业务逻辑应放在模型或Concern中,而非控制器。
ruby
undefinedConcern structure
Concern结构
module Dispatchable
extend ActiveSupport::Concern
included do
scope :available, -> { where(status: "pending") }
end
class_methods do
def claim!(batch_size)
# class-level behavior
end
end
end
undefinedmodule Dispatchable
extend ActiveSupport::Concern
included do
scope :available, -> { where(status: "pending") }
end
class_methods do
def claim!(batch_size)
# 类级别的行为逻辑
end
end
end
undefinedService Extraction
Service层提取
Extract when you see MULTIPLE of:
- Complex business rules (not just "it's long")
- Multiple models orchestrated
- External API interactions
- Reusable cross-controller logic
Service structure:
- Single public method
- Namespace by responsibility ()
Extraction::RegexExtractor - Constructor takes dependencies
- Return data structures, not domain objects
当出现以下多种情况时,应提取为Service:
- 复杂的业务规则(不只是"代码太长")
- 需要协调多个模型
- 涉及外部API交互
- 存在跨控制器可复用的逻辑
Service结构:
- 仅暴露一个公共方法
- 按职责划分命名空间(如)
Extraction::RegexExtractor - 构造函数接收依赖项
- 返回数据结构,而非领域对象
Modern Ruby Style
现代Ruby风格
ruby
undefinedruby
undefinedHash shorthand
Hash简写语法
{ id:, slug:, doc_type: kind }
{ id:, slug:, doc_type: kind }
Safe navigation
安全导航操作符
created_at&.iso8601
@setting ||= SlugSetting.active.find_by!(slug:)
created_at&.iso8601
@setting ||= SlugSetting.active.find_by!(slug:)
Keyword arguments
关键字参数
def extract(document_type:, subject:, filename:)
def process!(strategy: nil)
undefineddef extract(document_type:, subject:, filename:)
def process!(strategy: nil)
undefinedEnum Patterns
枚举模式
ruby
undefinedruby
undefinedFrozen arrays with validation
冻结数组搭配验证
STATUSES = %w[processed needs_review].freeze
enum :status, STATUSES.index_by(&:itself), validate: true
undefinedSTATUSES = %w[processed needs_review].freeze
enum :status, STATUSES.index_by(&:itself), validate: true
undefinedScope Patterns
作用域(Scope)模式
ruby
undefinedruby
undefinedGuard with .present?, chainable design
用.present?做守卫,保持链式调用设计
scope :by_slug, ->(slug) { where(slug:) if slug.present? }
scope :from_date, ->(date) { where(created_at: Date.parse(date).beginning_of_day..) if date.present? }
def self.filtered(params)
all.by_slug(params[:slug]).by_kind(params[:kind])
rescue ArgumentError
all
end
undefinedscope :by_slug, ->(slug) { where(slug:) if slug.present? }
scope :from_date, ->(date) { where(created_at: Date.parse(date).beginning_of_day..) if date.present? }
def self.filtered(params)
all.by_slug(params[:slug]).by_kind(params[:kind])
rescue ArgumentError
all
end
undefinedError Handling
错误处理
ruby
undefinedruby
undefinedDomain-specific errors
领域特定错误类
class InactiveSlug < StandardError; end
class InactiveSlug < StandardError; end
Log with context, re-raise for upstream
带上下文日志记录,重新抛出给上层处理
def handle_exception!(error:)
log_error("Exception #{error.class}: #{error.message}", error:)
mark_failed!(error.message)
raise
end
undefineddef handle_exception!(error:)
log_error("Exception #{error.class}: #{error.message}", error:)
mark_failed!(error.message)
raise
end
undefinedTesting (Minitest + Fixtures)
测试(Minitest + Fixtures)
ruby
test "describes expected behavior" do
email = emails(:two)
email.process
email.reload
assert_equal "finished", email.processing_status
endPrinciples:
- Behavior-driven: Test what, not how
- Fixture-based: Use for setup
emails(:two) - Mock externals: Stub S3, APIs, PDFs
- State verification: after operations
.reload - Helper methods: ,
build_valid_emailwith_stubbed_download
ruby
test "描述预期行为" do
email = emails(:two)
email.process
email.reload
assert_equal "finished", email.processing_status
end原则:
- 行为驱动:测试"做什么",而非"怎么做"
- 基于Fixture:使用进行测试准备
emails(:two) - 模拟外部依赖:Stub S3、API、PDF生成等
- 状态验证:操作后调用验证状态
.reload - 辅助方法:使用、
build_valid_email等with_stubbed_download
Naming (5-Second Rule)
命名规范(5秒原则)
If you can't understand in 5 seconds:
ruby
undefined如果5秒内无法理解命名含义:
ruby
undefinedFAIL
错误示例
show_in_frame
process_stuff
show_in_frame
process_stuff
PASS
正确示例
fact_check_modal
_fact_frame
undefinedfact_check_modal
_fact_frame
undefinedPerformance
性能优化
- Consider scale impact
- No premature caching
- KISS - Keep It Simple
- Indexes slow writes - add only when needed
- 考虑规模影响
- 避免过早缓存
- KISS原则 - 保持简单
- 索引会降低写入速度 - 仅在需要时添加