rails-conventions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rails Conventions

Rails 编码规范

Opinionated Rails patterns for clean, maintainable code.
一套用于编写整洁、可维护代码的Rails专属编码模式。

Core Philosophy

核心理念

  1. Duplication > Complexity: Simple duplicated code beats complex DRY abstractions
    • "I'd rather have four simple controllers than three complex ones"
  2. Testability = Quality: If it's hard to test, structure needs refactoring
  3. Adding controllers is never bad. Making controllers complex IS bad.
  1. 重复代码 > 复杂实现:简单的重复代码优于复杂的DRY抽象
    • "我宁愿有四个简单的控制器,也不要三个复杂的控制器"
  2. 可测试性 = 代码质量:如果代码难以测试,说明结构需要重构
  3. 新增控制器绝非坏事,但让控制器变得复杂才是问题。

Turbo Streams

Turbo Streams

Simple turbo streams MUST be inline in controllers:
ruby
undefined
简单的Turbo Streams必须内联在控制器中:
ruby
undefined

FAIL: 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") ]
undefined
render turbo_stream: [ turbo_stream.replace("post_#{@post.id}", partial: "posts/post", locals: { post: @post }), turbo_stream.remove("flash") ]
undefined

Controller & Concerns

控制器与Concern

Business logic belongs in models or concerns, not controllers.
ruby
undefined
业务逻辑应放在模型或Concern中,而非控制器。
ruby
undefined

Concern 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
undefined
module Dispatchable extend ActiveSupport::Concern
included do scope :available, -> { where(status: "pending") } end
class_methods do def claim!(batch_size) # 类级别的行为逻辑 end end end
undefined

Service 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
undefined
ruby
undefined

Hash 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)
undefined
def extract(document_type:, subject:, filename:) def process!(strategy: nil)
undefined

Enum Patterns

枚举模式

ruby
undefined
ruby
undefined

Frozen arrays with validation

冻结数组搭配验证

STATUSES = %w[processed needs_review].freeze enum :status, STATUSES.index_by(&:itself), validate: true
undefined
STATUSES = %w[processed needs_review].freeze enum :status, STATUSES.index_by(&:itself), validate: true
undefined

Scope Patterns

作用域(Scope)模式

ruby
undefined
ruby
undefined

Guard 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
undefined
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
undefined

Error Handling

错误处理

ruby
undefined
ruby
undefined

Domain-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
undefined
def handle_exception!(error:) log_error("Exception #{error.class}: #{error.message}", error:) mark_failed!(error.message) raise end
undefined

Testing (Minitest + Fixtures)

测试(Minitest + Fixtures)

ruby
test "describes expected behavior" do
  email = emails(:two)
  email.process
  email.reload
  assert_equal "finished", email.processing_status
end
Principles:
  • Behavior-driven: Test what, not how
  • Fixture-based: Use
    emails(:two)
    for setup
  • Mock externals: Stub S3, APIs, PDFs
  • State verification:
    .reload
    after operations
  • Helper methods:
    build_valid_email
    ,
    with_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
undefined

FAIL

错误示例

show_in_frame process_stuff
show_in_frame process_stuff

PASS

正确示例

fact_check_modal _fact_frame
undefined
fact_check_modal _fact_frame
undefined

Performance

性能优化

  • Consider scale impact
  • No premature caching
  • KISS - Keep It Simple
  • Indexes slow writes - add only when needed
  • 考虑规模影响
  • 避免过早缓存
  • KISS原则 - 保持简单
  • 索引会降低写入速度 - 仅在需要时添加