mcpField
R
ruby-on-rails-best-practices

ruby-on-rails-best-practices

github.com/sergiodxa开发92280 次安装38 周前更新
代码仓库

关于 ruby-on-rails-best-practices

ruby-on-rails-best-practices 汇集 Basecamp 生产级 Rails 应用中的架构模式与编码约定,适合开发和审查 Rails 项目的工程师。其 16 条规则覆盖模型组织、后台任务、控制器、请求上下文、实时功能和测试,强调遵循 Rails 约定并减少额外层次。

主要功能

  1. 0116 条规则覆盖 6 个类别
  2. 02模型与 concern 的组织约定
  3. 03后台任务与控制器设计指引
  4. 04Current 和回调使用模式
  5. 05Turbo 实时功能与测试建议

适用场景

  1. 01规划 Rails 模型与 concern 的代码位置
  2. 02审查控制器和后台任务的职责划分
  3. 03为 Turbo 实时功能选择广播模式
  4. 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.md
callbacks-patterns.md
code-style.md
concern-naming.md
current-attributes.md
current-in-other-contexts.md
fixtures-testing.md
model-scoped-concerns.md
nested-service-objects.md
paired-async-methods.md
resource-controllers.md
scoping-concerns.md
template-method-concerns.md
thin-controllers.md
thin-jobs.md
turbo-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 约定,只做最少的补充:

  1. 丰富的模型,精简的控制器——将领域逻辑放在模型和关注点中
  2. 不设服务对象层——控制器直接与模型交互
  3. 代码就近组织——将关注点、任务和服务放在其所服务的模型附近
  4. 显式优于隐式——显式调用广播,而不是通过回调触发
  5. 约定优于配置——遵循命名模式,确保行为可预测

评论

支持 Markdown