首页
/ ECC Ruby Patterns 实战指南:面向 Ruby 与 Rails 的 Agent 编码规约全解

ECC Ruby Patterns 实战指南:面向 Ruby 与 Rails 的 Agent 编码规约全解

2026-09-06 18:54:32作者:江焘钦

本指南以仓库中的语言级 steering 文件 ruby-patterns.md 为主体脉络,系统讲解在 ECC 驱动的 Agent 工作流中编写 Ruby / Rails 代码时应遵循的技术选型、架构分层、安全基线、后台任务与测试策略,并横向对照同主题的 rules/ruby 规范族、真实项目模板 rails-app-CLAUDE.md 与后端通用技能 backend-patterns,帮你把"约束文件里的一句话规则"还原为可直接落地、可复制运行的工程实践。读完你既能理解这些规约为何被逐条固化进仓库,也能按图索骥地为自己的 Rails 项目搭建同样的开发纪律。

一、这份文档在 ECC 仓库中的定位:语言级 steering 文件

在 Everything Claude Code(ECC)类工作流仓库中,.kiro 目录专门存放"让 Agent 按团队规约工作"的指导性文件。打开 ruby-patterns.md 即可看到它的生效方式由 YAML frontmatter 控制:

---
inclusion: fileMatch
fileMatchPattern: "*.rb"
description: Ruby-specific patterns and Rails best practices.
---

这意味着该文件属于 .kiro/steering/ 下 22 个 steering 文件中的 fileMatch 型规则——它与 inclusion: auto 的通用规则(如 coding-style.mdpatterns.md)不同,只有当 Agent 正在编辑 *.rb 等 Ruby 文件时才会被自动装载(参见 .kiro/README.md 对 Steering Files 机制的说明)。这是一种典型的"按文件类型按需注入上下文"的设计:通用模式始终在场,语言专属细节只在相关语言出现时进入上下文,避免每个会话都背一整套无关噪音。

文件开头明确写到 "This file extends the common patterns with Ruby and Rails specific content",即它是通用模式文件 .kiro/steering/patterns.md(仓库模式、API 响应信封、骨架项目评估法)的 Ruby/Rails 方言扩展。仓库中与之配套的同一语言家族还包括 rules/ruby 目录下的五份规则:coding-style.mdpatterns.mdsecurity.mdtesting.mdhooks.md,它们共同构成"指导文件 + 审查规则 + 钩子"的完整闭环。

二、语言与工程标准:先选对底座

原文档在 Standards 一节给出三条硬约束,仓库其他文件进一步给出了补充理由与判断前提:

规则 出处 补充说明(依据仓库内容)
新 Rails 项目面向 Ruby 3.3+ .kiro/steering/ruby-patterns.md rules/ruby/coding-style.md 补充:除非项目已锁定更旧的受支持运行时
新文件添加 # frozen_string_literal: true 同上 仅在项目已采用该约定时使用;rails-app-CLAUDE.md 的工程规范要求每个 Ruby 文件顶部都有此行
偏好清晰 Ruby,避免炫技元编程 同上 同上文件进一步约束:DSL 密集的代码要隔离在窄而可测试的边界之后

值得补充的是 rails-app-CLAUDE.md 给出的完整编码基线,可作为"清晰 Ruby"的操作化定义:

  • 现代哈希语法 key:,除非键不是 Symbol 才用火箭 =>
  • 默认双引号,字符串内已含双引号时才用单引号;
  • 两空格缩进,禁止 Tab;
  • 类用 PascalCase、方法/变量用 snake_case、常量用 UPPER_SNAKE_CASE;
  • 控制器保持在 80 行以内、模型 200 行以内,超限即抽取;
  • 生产代码中禁止 putsppdebuggerbinding.pry,统一走 Rails.logger.<level>

错误处理层面,rules/ruby/coding-style.md 要求只 rescue 具体异常,避免宽泛的 rescue StandardError(除非重抛或保留操作上下文),并用 ActiveSupport::Notifications 或应用 logger 记录运维事件——这些正好与原文档"清晰优先"的价值取向互为表里。

三、格式与 Lint:让本地与 CI 跑同一条命令

原文档给出了两条 RuboCop 命令:

bundle exec rubocop
bundle exec rubocop -A
  • bundle exec rubocop:只读报告,列出全部违规项;
  • bundle exec rubocop -A自动安全修复(autocorrect),可用于本地快速清理机械性问题。

结合 rules/ruby/coding-style.mdrules/ruby/hooks.md,可进一步落实三点实操细节:

  1. 以项目检入的 RuboCop 配置为准。Rails 8+ 新项目建议从官方默认风格 rubocop-rails-omakase 起步,仅在代码库确有真实惯例时才做定制。
  2. 把格式化/Lint 命令收敛到 binstub 或脚本后面,保证 CI 与本地执行的永远是同一套命令,例如 bin/rubocop
  3. 不要随意在行内静默禁用 cop# rubocop:disable),除非例外足够窄、有文档说明且难以用更干净的代码表达。
  4. 若将 RuboCop 接入钩子,应在编辑 Ruby 文件后运行 bundle exec rubocop -A <file> 或项目更安全的格式化命令,而不是全局跑一遍。

四、架构分层:Rails Way First,再谈抽取

原文档的核心立场是 "Rails Way First"

  • 小/中型功能先从纯 Rails MVC 与 Active Record 约定开始;
  • 只有当 model/controller 承担了多重职责时,才引入 service objects、query objects、form objects(rules/ruby/patterns.md 还补充了 decorators、presenters);
  • 保持 controller 是"传输层":只做 auth、参数处理、响应形状三件事。

关于抽取对象的命名,rules/ruby/patterns.md 有一条容易被忽略却极有价值的原则:

Name extracted objects after the business operation they perform, not after generic layers like Manager or Processor.

即按业务操作命名(Invoices::Create),而不是按通用层命名(InvoiceCreatorXxxProcessor)。这与 rails-app-CLAUDE.md 中 service objects 要按领域命名空间组织(app/services/ 下如 Invoices::Create)的要求完全一致。

来看模板文件中"瘦控制器 + Service Object + Result 返回"的经典组合(节选自 rails-app-CLAUDE.md):

# app/services/invoices/create.rb —— 领域服务:一个方法入口、明确返回 Result
module Invoices
  class Create
    Result = Data.define(:success?, :invoice, :errors)

    def self.call(...) = new(...).call

    def initialize(params:, user:)
      @params = params
      @user = user
    end

    def call
      invoice = build_invoice

      ApplicationRecord.transaction do
        invoice.save!
      end

      begin
        send_notifications(invoice)
      rescue StandardError => e
        Rails.logger.error("Notification dispatch failed for invoice #{invoice.id}: #{e.message}")
      end

      Result.new(success?: true, invoice: invoice, errors: nil)
    rescue ActiveRecord::RecordInvalid => e
      Result.new(success?: false, invoice: e.record, errors: e.record.errors)
    end

    private

    attr_reader :params, :user

    def build_invoice
      invoice = user.invoices.new(params.except(:line_items))
      invoice.line_items.build(params[:line_items])
      invoice.total = invoice.line_items.sum(&:amount)
      invoice
    end
  end
end

# app/controllers/invoices_controller.rb —— 传输层只做三件事
class InvoicesController < ApplicationController
  before_action :require_authentication

  def create
    authorize Invoice

    result = Invoices::Create.call(params: invoice_params, user: current_user)

    if result.success?
      redirect_to result.invoice, notice: "Invoice created"
    else
      @invoice = result.invoice
      render :new, status: :unprocessable_entity
    end
  end

  private

  def invoice_params
    params.require(:invoice).permit(:customer_id, line_items: %i[description amount])
  end
end

这段代码同时示范了原文档与通用模式 .kiro/steering/patterns.md 中"API 响应信封(success + data + error)"思想在服务层 Result 对象上的体现:成功与失败都通过返回值显式表达,服务边界内不靠抛异常做流程控制。这与 rails-app-CLAUDE.md 的错误处理约定一一对应——业务失败不跨服务边界抛错、期望内异常在服务内部捕获并写进 Result。

查询对象示例同样可在模板中找到完整版(rails-app-CLAUDE.md):

# app/queries/invoices/overdue.rb
module Invoices
  class Overdue
    def self.call(...) = new(...).call

    def initialize(scope: Invoice.all, as_of: Time.current)
      @scope = scope
      @as_of = as_of
    end

    def call
      scope
        .where(status: :sent)
        .where(due_date: ..as_of)
        .where.not(id: paid_invoice_ids)
        .includes(:customer, :line_items)
    end

    private

    attr_reader :scope, :as_of

    def paid_invoice_ids
      Payment.where(created_at: ..as_of).pluck(:invoice_id)
    end
  end
end

注意 scope 的默认值是 Invoice.allas_of 默认 Time.current——这使查询对象天然可组合、可注入时间做测试,正是"query objects 用于可复用、可组合的 Active Record 查询"的注解。

五、持久化:PostgreSQL 优先 + 一切动态值参数化

原文档对持久化的三条主张:

  1. 多主机生产环境 Rails 应用优先选用 PostgreSQL
  2. 原生 SQL 要藏在 query objects 或 model scopes 之后;
  3. 每一个动态值都必须参数化

rules/ruby/patterns.md 补充了一个容易被忽略的细分场景:Rails 8 默认的 SQLite 适配对单主机或负载温和的部署可行,但不应自动视为共享多服务系统的选择。这使选型判断从"默认 Postgres"细化为"按部署拓扑决策"。

关于 N+1 与回调边界,rails-app-CLAUDE.md 给出了可直接复制进团队规范的具体条款,可视为对"Raw SQL 纪律"的近邻约束:

# BAD: N+1 query —— 每条 post 触发一次 author 查询
posts = Post.published
posts.each { |post| post.author.name }

# GOOD: 一条查询完成 eager load
posts = Post.published.includes(:author)
posts.each { |post| post.author.name }

配套建议还包括:默认 eager load 关联、避免 default_scope(改用调用方主动选择的具名 scope)、按需在 .includes / .preload / .eager_load 间选择、对列表页展示 count 的 has_many 使用 counter caches、回调只做数据规整(如 before_validation :normalize_email),任何带副作用的行为都应放进 service。

六、后台任务:Solid Queue 与 Sidekiq 的分场景决策

原文档给出了一个明确的"分水岭"决策:

  • Solid Queue:全新 Rails 8 应用 + 吞吐量温和 + 部署简单时使用;
  • Sidekiq:需要成熟可观测性、高吞吐、或已存在 Redis 基础设施时使用。

rules/ruby/patterns.md 把决策矩阵扩展到了缓存与实时通道:Solid Cache / Solid Cable 在部署模型匹配时是 Rails 8 默认无 Redis 方案;而当存在跨服务共享行为、高扇出(high fanout)或需要高级数据结构时再回到 Redis。对应的环境变量配置可参考 rails-app-CLAUDE.md

# SolidQueue / SolidCache / SolidCable 默认复用主数据库
# 高负载时可为它们单独建库:
QUEUE_DATABASE_URL=postgres://user:pass@host:5432/myapp_queue
CACHE_DATABASE_URL=postgres://user:pass@host:5432/myapp_cache

模板文件还沉淀了写 Job 的四条工程铁律(rails-app-CLAUDE.md):

  1. 向 Job 传 ID 而非 Record 对象——避免记录在入队与执行之间被删除时抛出 ActiveJob::DeserializationError
  2. perform 必须幂等——假设它会执行不止一次;
  3. 显式声明重试策略:retry_ondiscard_on
  4. 按动作命名 Job(SendInvoiceJob),而不是按名词(InvoiceJob)。

一个同时体现幂等与重试约定的示例(rails-app-CLAUDE.md):

# app/jobs/export_accounting_job.rb
class ExportAccountingJob < ApplicationJob
  queue_as :exports

  retry_on AccountingApi::TransientError, wait: :polynomially_longer, attempts: 5
  discard_on AccountingApi::PermanentError

  def perform(invoice_id)
    invoice = Invoice.find(invoice_id)
    return if invoice.exported_at.present?  # 本地幂等检查

    idempotency_key = "invoice-export-#{invoice.id}"
    AccountingApi.export(invoice, idempotency_key: idempotency_key)
    invoice.update!(exported_at: Time.current)
  end
end

七、前端技术选型:默认 Hotwire,交互复杂度说了算

原文档的前端立场是"服务端渲染优先、默认 Hotwire",并给出了判定边界:

  • Rails 服务端渲染应用优先选用 Hotwire 全家桶(Turbo、Stimulus、Importmap、Propshaft);
  • 只有当交互复杂度或既有产品架构/团队归属确实需要额外客户端表面积时,才引入 React / Vue / Inertia

rules/ruby/patterns.md 进一步强调了渲染关注点的隔离:view components、partials、presenters 只负责渲染决策,持久化与授权逻辑不得进入模板。配套的真实工程约束(rails-app-CLAUDE.md)可以作为验收清单:

  • Hotwire 之上优先 ViewComponent(有复杂条件、接收多参数、出现三处以上的视图逻辑才用);简单呈现用 ERB partial;
  • Turbo Frames 负责局部页面更新,Turbo Streams 负责服务端驱动的多区域响应;
  • Action Cable 连接在 ApplicationCable::Connection#connect 中鉴权,订阅在每个 channel 的 subscribed 中授权后才 stream_from
  • 视图更新优先走 Turbo Stream 广播(broadcasts_tobroadcast_replace_later_to)而非手写 channel;
  • 把广播视为公开展示层,绝不在其中携带订阅者不该看到的数据。

八、认证选型:Rails 8 生成器起步,复杂需求再上 Devise

原文档的认证策略非常务实,按需求复杂度二分:

场景 方案
常规 session 认证(含密码重置) Rails 8 authentication generatorbin/rails generate authentication
涉及 OAuth、MFA、confirmable/lockable 流程 Devise

rules/ruby/security.md 对会话安全补充了三个不能省的动作:登录与权限变更后轮换 session;账户恢复流程要带过期时间、一次性 token、限流与审计日志;多模型认证或已有大量 Devise 足迹时选择 Devise。rails-app-CLAUDE.md 还展示了它与授权层的衔接:session 认证用于整页应用、token 认证用于内嵌 API,授权走 Pundit 且每个 controller action 都有 authorize 或带文档说明的 skip_authorization。这条组合正是"认证归认证、授权归授权、控制器只做传输"原则在真实项目中的形态。

九、安全基线:从 CSRF 到依赖审计的一条龙命令

原文档 Security 一节浓缩为四条铁律 + 两条审计命令:

  • 面向会改变状态的浏览器请求,保持 CSRF 保护开启
  • 在 mass assignment 之前使用 strong parameters 或类型化边界对象
  • 密钥存入 Rails credentials 或环境变量,绝不允许提交明文;
  • 优先使用 Active Record 查询 API 与参数化 SQL,绝不把用户输入插值进 SQL
bundle exec bundle-audit check --update
bundle exec brakeman --no-progress

rules/ruby/security.md 把这条安全面扩成了五个可审查维度,可作为原文档的逐条展开:

  1. Rails 默认值:CSRF、strong parameters、secrets 管理三条之上,还要求不提交 .env 副本;
  2. SQL 与 Active Record:请求、cookie、header、job、webhook 值一律不得插值进 SQL 字符串;安全敏感的回调副作用必须显式且有测试覆盖;
  3. 认证与会话:登录/权限变更后轮换 session;账户恢复流程需过期时间 + 一次性 token + 限流 + 审计;
  4. 依赖:lockfile 变更时即跑 audit;引入新 gem 前审查维护者活跃度、原生扩展风险、传递依赖,以及"同样的行为 Rails 核心是否已能实现";
  5. Web 安全面:模板输出默认转义,html_saferaw、自定义 sanitizer 一律视为敏感代码;上传文件按 content type、扩展名、大小与存储目标校验;后台任务、webhooks、Action Cable 消息、Turbo Stream 输入都被视为不可信边界

原文档 Security 与 Testing 中的命令在 CI 闸门与钩子侧还有对应的落地形态:rules/ruby/hooks.md 建议在安全敏感的 Rails 修改后跑 bundle exec brakeman --no-progress,在 Gemfile / Gemfile.lock 变化且项目装有 bundler-audit 时跑 audit,并对"禁用 CSRF、扩大 mass assignment、加无参数化裸 SQL、破坏性且不可逆的迁移"给出警告。完整 CI 建议命令集为:

bundle exec rubocop
bundle exec brakeman --no-progress
bin/rails test
bundle exec rspec

原则是只用项目里真实存在的命令,未获维护者批准不得为钩子安装新依赖

十、测试策略:金字塔纪律与工具归属

原文档的测试主张清晰区分了两种情况:

  • 跟随 Rails 默认测试栈的应用用 Minitest
  • 项目已确立 RSpec 就用 RSpecrules/ruby/testing.md 补充:同一功能区域内不要混用两者)。

分层要求上,原文档要求快领域行为放 model/service/query 测试、仅浏览器关键路径用 Capybara system tests。测试金字塔的完整展开(rules/ruby/testing.md)是:

  • model / service / query / policy / job 测试承载快的领域行为
  • request/controller 测试覆盖 HTTP 契约、认证行为、重定向、状态码与响应形状;
  • Capybara system tests 只用于浏览器关键流程,保持聚焦与稳定;
  • 后台任务用单元测试测行为、集成测试测队列/入队契约
  • fixture 是项目默认且数据图小时使用;复杂构造与 traits 才上 factory_bot;测试数据贴近被测行为,避免隐藏成本的全局 fixture;
  • 使用 SimpleCov 时把阈值放在 CI,且不以低价值测试去刷分支覆盖率;bug 修复先补回归测试。

原文档给出的运行命令为:

bin/rails test
bundle exec rspec

rules/ruby/testing.md 可扩展为"最窄匹配"执行法(也正是钩子触发的粒度):

bin/rails test
bin/rails test test/models/user_test.rb
bundle exec rspec
bundle exec rspec spec/models/user_spec.rb

配套的 RSpec 测试示例(服务对象 + Result + 邮件入队断言)可参见 rails-app-CLAUDE.md;模板还建议以 90% 行覆盖为下限而非目标,"覆盖 85% 的锋利测试优于覆盖 100% 的堆积测试",system tests 默认 rack_test 驱动、仅在需要 JavaScript 时才切换到 headless Chrome。

十一、闭环落地:规则如何与 Agent、技能与钩子协同

原文档在末尾以两条 Reference 指向技能,实际上勾勒了这套 Ruby 规约的完整生态:

See skill: `backend-patterns` for service boundaries and adapter patterns.
See skill: `security-review` for secure-by-default review patterns.

两条引用分别指向仓库技能目录下的 backend-patterns SKILLsecurity-review SKILL(根目录同名的 skills/backend-patternsskills/security-review 为该技能在 ECC 主仓的源版本),再加上 Ruby 专属 agent 如 python-reviewer / ruby 系审查者 可类比扩展,形成如下工作链路:

  1. Agent 开始编辑 *.rb 文件 → 自动装载 ruby-patterns.md,并叠加通用 patterns.md
  2. 需要确定服务边界/适配器模式时 → 按需调用 backend-patterns 技能;
  3. 涉及安全默认值时 → 调用 security-review 技能做 secure-by-default 检查;
  4. 写完文件 → rules/ruby/hooks.md 中的钩子建议在本地就近跑 RuboCop / Brakeman / 最窄测试 / bundler audit;
  5. 提交前 → 以 bundle exec rubocopbundle exec brakeman --no-progressbin/rails test/bundle exec rspec 组成本项目的 CI 闸门。

若希望看到这套规约落进真实 Rails 8 项目的完整形态(含目录结构、环境变量、部署与 ECC 工作流 /plan/tdd/code-review/security-scan),可直接参考仓库内置的 rails-app-CLAUDE.md

小结:把约束变成纪律

.kiro/steering/ruby-patterns.md 的价值不在于它的每一条都是新知识,而在于它把一群团队反复踩过的 Ruby/Rails 决策压缩成了 Agent 可自动装载、可逐条执行、可被引用追溯的规则。当你把本指南中的命令、矩阵与代码模式反哺回自己的项目时,不妨遵循仓库自身的沉淀方式:通用原则放进 auto 型文件,Ruby 专属内容挂在 fileMatchPattern: "*.rb" 上,安全、测试与钩子各归其位,再让 lessons-learned 型的项目文件持续吸收新教训——这正是 ECC 类工作流让工程规约"活"起来的方式。

延伸阅读(仓库内相对路径)

登录后查看全文
热门项目推荐
相关项目推荐