关于 ruby-on-rails-best-practices
ruby-on-rails-best-practices 汇集 Basecamp 生产级 Rails 应用中的架构模式与编码约定,适合开发和审查 Rails 项目的工程师。其 16 条规则覆盖模型组织、后台任务、控制器、请求上下文、实时功能和测试,强调遵循 Rails 约定并减少额外层次。
主要功能
- 0116 条规则覆盖 6 个类别
- 02模型与 concern 的组织约定
- 03后台任务与控制器设计指引
- 04Current 和回调使用模式
- 05Turbo 实时功能与测试建议
适用场景
- 01规划 Rails 模型与 concern 的代码位置
- 02审查控制器和后台任务的职责划分
- 03为 Turbo 实时功能选择广播模式
- 04统一 Rails 项目的测试和代码组织方式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sergiodxa/ agent-skills --skill ruby-on-rails-best-practices安装后在 Claude Code 中输入/ruby-on-rails-best-practices确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
rules/association-extensions.mdcallbacks-patterns.mdcode-style.mdconcern-naming.mdcurrent-attributes.mdcurrent-in-other-contexts.mdfixtures-testing.mdmodel-scoped-concerns.mdnested-service-objects.mdpaired-async-methods.mdresource-controllers.mdscoping-concerns.mdtemplate-method-concerns.mdthin-controllers.mdthin-jobs.mdturbo-broadcasts.md详细介绍
Ruby on Rails 最佳实践
从 Basecamp 的生产级 Rails 应用(Fizzy 和 Campfire)中提炼出的架构模式和编码约定。包含 6 个类别中的 16 条规则,重点关注代码组织、可维护性,以及遵循经 Basecamp 改进的“Rails 之道”。
适用场景
在以下情况下参考这些指南:
- 组织模型、关注点和控制器
- 编写后台任务
- 使用 Turbo Streams 实现实时功能
- 决定代码应该放在哪里
- 为 Rails 应用编写测试
- 审查 Rails 代码的架构一致性
规则摘要
模型组织(HIGH)
model-scoped-concerns - @rules/model-scoped-concerns.md
将模型专属的关注点放在 app/models/model_name/ 中,而不是 app/models/concerns/ 中。
# Directory structure
app/models/
├── card.rb
├── card/
│ ├── closeable.rb # Card::Closeable
│ ├── searchable.rb # Card::Searchable
│ └── assignable.rb # Card::Assignable
# app/models/card.rb
class Card < ApplicationRecord
include Closeable, Searchable, Assignable
# Ruby resolves from Card:: namespace first
end
concern-naming - @rules/concern-naming.md
行为关注点使用 -able 后缀,功能关注点使用名词。
# Behaviors: -able suffix
module Card::Closeable # Can be closed
module Card::Searchable # Can be searched
module User::Mentionable # Can be mentioned
# Features: nouns
module User::Avatar # Has avatar
module User::Role # Has role
module Card::Mentions # Has @mentions
template-method-concerns - @rules/template-method-concerns.md
在共享关注点中使用模板方法,以支持可自定义的行为。
# app/models/concerns/searchable.rb (shared)
module Searchable
def search_title
raise NotImplementedError
end
end
# app/models/card/searchable.rb (model-specific)
module Card::Searchable
include ::Searchable
def search_title
title # Implement the hook
end
end
后台任务(HIGH)
paired-async-methods - @rules/paired-async-methods.md
为同步方法配对提供可入队执行任务的 _later 变体。
# app/models/card/readable.rb
def remove_inaccessible_notifications
# Sync implementation
end
private
def remove_inaccessible_notifications_later
Card::RemoveInaccessibleNotificationsJob.perform_later(self)
end
# app/jobs/card/remove_inaccessible_notifications_job.rb
class Card::RemoveInaccessibleNotificationsJob < ApplicationJob
def perform(card)
card.remove_inaccessible_notifications
end
end
thin-jobs - @rules/thin-jobs.md
任务调用模型方法。所有逻辑都放在模型中。
# Bad: Logic in job
class ProcessOrderJob < ApplicationJob
def perform(order)
order.items.each { |i| i.product.decrement!(:stock) }
order.update!(status: :processing)
end
end
# Good: Job delegates to model
class ProcessOrderJob < ApplicationJob
def perform(order)
order.process # Single method call
end
end
控制器(HIGH)
resource-controllers - @rules/resource-controllers.md
状态变更应创建资源控制器,而不是自定义操作。
# Bad: Custom actions
resources :cards do
post :close
post :reopen
end
# Good: Resource controllers
resources :cards do
resource :closure, only: [:create, :destroy]
end
# app/controllers/cards/closures_controller.rb
class Cards::ClosuresController < ApplicationController
def create
@card.close
end
def destroy
@card.reopen
end
end
scoping-concerns - @rules/scoping-concerns.md
使用 CardScoped 等关注点来设置嵌套资源。
# app/controllers/concerns/card_scoped.rb
module CardScoped
extend ActiveSupport::Concern
included do
before_action :set_card
end
private
def set_card
@card = Current.user.accessible_cards.find_by!(number: params[:card_id])
end
end
# Usage
class Cards::CommentsController < ApplicationController
include CardScoped
end
thin-controllers - @rules/thin-controllers.md
控制器直接调用功能丰富的模型 API。不使用服务对象。
# Good: Thin controller, rich model
class Cards::ClosuresController < ApplicationController
include CardScoped
def create
@card.close # All logic in model
end
end
请求上下文(MEDIUM)
current-attributes - @rules/current-attributes.md
使用 Current 存储请求范围的数据,并通过级联 setter 设置属性。
class Current < ActiveSupport::CurrentAttributes
attribute :session, :user, :account
def session=(value)
super(value)
self.user = session&.user
end
end
current-in-other-contexts - @rules/current-in-other-contexts.md
Current 只会在 Web 请求中自动填充。任务、邮件程序和频道需要显式设置。
# Jobs: extend ActiveJob to serialize/restore Current.account
# Mailers from jobs: wrap in Current.with_account { mailer.deliver }
# Channels: set Current in Connection#connect
关联与回调(MEDIUM)
association-extensions - @rules/association-extensions.md
根据上下文需求,在关联扩展和模型类方法之间做出选择。
# Use extension when you need parent context (proxy_association.owner)
has_many :accesses do
def grant_to(users)
board = proxy_association.owner
Access.insert_all(users.map { |u| { user_id: u.id, board_id: board.id, account_id: board.account_id } })
end
end
# Use class method when operation is independent
class Access
def self.grant(board:, users:)
insert_all(users.map { |u| { user_id: u.id, board_id: board.id } })
end
end
callbacks-patterns - @rules/callbacks-patterns.md
任务使用 after_commit,简单操作使用内联 lambda。
# Jobs: after_commit
after_create_commit :notify_recipients_later
# Simple ops: inline lambda
after_save -> { board.touch }, if: :published?
# Conditional: remember and check pattern
before_update :remember_changes
after_update_commit :process_changes, if: :should_process?
Turbo 与实时功能(MEDIUM)
turbo-broadcasts - @rules/turbo-broadcasts.md
由控制器显式广播,而不是使用回调。
# app/models/message/broadcasts.rb
module Message::Broadcasts
def broadcast_create
broadcast_append_to room, :messages, target: [room, :messages]
end
end
# Controller calls explicitly
def create
@message = @room.messages.create!(message_params)
@message.broadcast_create
end
测试(MEDIUM)
fixtures-testing - @rules/fixtures-testing.md
使用 fixtures,而不是工厂。让测试中的关注点结构与生产代码保持一致。
# test/fixtures/cards.yml
logo:
title: The logo isn't big enough
board: writebook
creator: david
# test/models/card/closeable_test.rb
class Card::CloseableTest < ActiveSupport::TestCase
test "close creates closure" do
card = cards(:logo)
assert_difference -> { Closure.count } do
card.close
end
end
end
代码组织(LOW-MEDIUM)
nested-service-objects - @rules/nested-service-objects.md
将服务对象放在模型命名空间下,而不是 app/services 中。
# Good: app/models/card/activity_spike/detector.rb
class Card::ActivitySpike::Detector
def initialize(card)
@card = card
end
def detect
# ...
end
end
code-style - @rules/code-style.md
优先使用展开式条件语句,并按调用顺序排列方法。
# Expanded conditionals
def find_record
if record = find_by_id(id)
record
else
NullRecord.new
end
end
# Method ordering: caller before callees
def process
step_one
step_two
end
private
def step_one; end
def step_two; end
理念
这些模式体现了“原味 Rails”——遵循 Rails 约定,只做最少的补充:
- 丰富的模型,精简的控制器——将领域逻辑放在模型和关注点中
- 不设服务对象层——控制器直接与模型交互
- 代码就近组织——将关注点、任务和服务放在其所服务的模型附近
- 显式优于隐式——显式调用广播,而不是通过回调触发
- 约定优于配置——遵循命名模式,确保行为可预测