mcpField
R
ruby-on-rails-best-practices

ruby-on-rails-best-practices

github.com/midudev开发7.0k62 次安装今天更新
代码仓库

关于 ruby-on-rails-best-practices

基于 Basecamp 应用 Fizzy 和 Campfire 实践的 Rails 指导 Skill,帮助开发者按 Rails 惯例组织代码、任务、控制器、实时功能和测试。

主要功能

  1. 01模型与 Concern 组织
  2. 02后台任务约定
  3. 03资源导向控制器
  4. 04请求上下文处理
  5. 05Turbo 广播模式
  6. 06基于 Fixtures 的测试

适用场景

  1. 01组织模型专属 Concern
  2. 02检查控制器与任务的职责划分
  3. 03实现 Turbo Stream 广播
  4. 04选择 Rails 测试模式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/midudev/autoskills --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