关于 rails-best-practices-core
为日常 Rails 开发提供基线实践的技能,内容涵盖建模、路由、授权、依赖选择与代码评审。它强调 Rails 内置约定、清晰代码和数据安全。
主要功能
- 01优先采用 Rails 约定与内置功能
- 02将领域行为放入模型
- 03以记录表达状态与行为
- 04通过数据库约束保障不变量
- 05涵盖 REST 路由与授权模式
- 06提供代码评审检查项
适用场景
- 01为 Rails 功能变更确定实现模式
- 02设计领域模型与资源路由
- 03检查数据约束和多租户边界
- 04评估新增依赖是否必要
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marckohlbrugge/ 37signals-skills --skill rails-best-practices-core安装后在 Claude Code 中输入/rails-best-practices-core确认已加载。
详细介绍
Rails 最佳实践核心
将此用作 Rails 开发的默认基线。内容提炼自 37signals 的代码库(Campfire、Fizzy)以及 DHH 的代码审查模式。
核心默认原则
- 优先使用清晰、明确的代码,而非巧妙的抽象。抽象必须证明其价值;如果找不到至少 3 种需要它的变体,就直接内联。
- 保持控制器精简,将领域行为放在模型中。
- 添加 gem 之前,优先采用 Rails 约定和内置功能。
- 使用领域概念来建模状态和行为,而非临时标志。
- 通过所有权边界限定租户/用户数据的范围。
- 对硬性不变量优先使用数据库约束;只有需要向用户显示错误消息时,才在 AR 中验证。
- 保持接口精简;不要添加任何地方都未使用的公共方法。
- 优先在写入时计算,而非在读取时进行高开销组合(计数缓存、委托类型、预计算汇总、不需要回调时使用
dependent: :delete_all)。 - 在现代 Rails 中,使用
params.expect(...)处理强参数。 - 让错误抛出:使用 bang 方法(
create!),在边界处处理异常。只有存在对应的非 bang 方法时,才使用!。 - 修复根本原因,而非表面症状(例如,针对竞态使用
enqueue_after_transaction_commit,而非重试逻辑)。 - 与行为变更在同一个 PR 中提交测试。
建模模式
- 将状态建模为记录,而非布尔值。 不要使用
closed: boolean,而是创建一个包含creator和时间戳的Closure记录。这样无需额外工作就能记录由谁在何时执行操作,而且限定范围也很简单:
has_one :closure, dependent: :destroy
scope :closed, -> { joins(:closure) }
scope :open, -> { where.missing(:closure) }
- 将大型模型拆分为按能力命名的 concern(
Closeable、Watchable、Assignable),每个 concern 都自成一体(包含关联、作用域和方法),约 50-150 行,且职责内聚。领域切片优先使用模型命名空间下的嵌套模块(在app/models/card/closeable.rb中定义Card::Closeable);app/models/concerns/仅用于真正跨模型的行为。绝不要提取只包含私有方法的 concern。 - PORO 放在
app/models/,而非app/services/:展示对象(Event::Description)、复杂操作(SystemCommenter)、视图上下文集合(User::Filtering)。它们与模型相关,而非与控制器相关。 - 通过 lambda 设置默认值:
belongs_to :creator, class_name: "User", default: -> { Current.user };belongs_to :account, default: -> { board.account }。 - 使用 Current 属性保存请求上下文(
Current.user、Current.account),并设置级联赋值器(赋值session会解析出identity,再由此解析出该账户的user)。 - 回调用于设置和清理,而非业务逻辑。 尽量减少回调数量。
- 优先考虑的 Rails 简便功能:
normalizes(验证前清理数据)、store_accessor(JSON 列)、delegated_type(异构集合)、generates_token_for(有过期时间的签名令牌)、通过enum :status, %w[drafted published].index_by(&:itself)定义字符串枚举、after_save_commit、用于缓存失效的touch: true链、delegate。 - 使用关联扩展执行批量领域操作: 在
has_many代理上定义grant_to/revise;批量创建时使用insert_all,并在无需回调的连接表上使用dependent: :delete_all。 - 易读的 URL: 用每个租户内的
number重写to_param,而非公开原始 ID/UUID。
命名
- 花时间斟酌名称——命名就是设计。
Closure胜过CardClose;Mention胜过UserReference。 - 使用正向名称:用
active,而非not_deleted;用visible,而非not_hidden。 - 关联名称应体现语义角色:使用
belongs_to :creator, class_name: "User",而非belongs_to :user。 - 优先使用领域术语,而非技术术语:用
quota.depleted?,而非quota.over_limit?。 - 作用域名称应面向业务:使用
:active、:unassigned、:golden,而非类似 SQL 的:without_pop。 - 保持领域术语一致:不要用
source/resource/container指代同一个概念。
REST 与路由
- 一切皆为 CRUD:将动词转为名词。关闭 →
resource :closure(POST 关闭,DELETE 重新打开);发布 →resource :publication。不要自定义成员操作。 - 对每个父级仅有一个的状态使用单数
resource;使用scope module:对嵌套控制器分组(Cards::ClosuresController);对于深层级结构使用浅层嵌套。 - 在嵌套控制器之间共享资源作用域控制器关注点(
CardScoped通过Current.user.accessible_cards.find_by!(...)设置@card),包括共享的 Turbo 渲染辅助方法。 - 使用
resolve "Comment",将多态 URL 生成为指向父级并带锚点的 URL。 - 通过
respond_to让同一控制器处理 HTML/Turbo/JSON,而不是单独设置 API 命名空间。
授权
- 不使用 Pundit/CanCanCan:在模型上使用简单的谓词方法(
card.editable_by?(user)、user.can_administer_board?(board))。 - 控制器进行检查(
head :forbidden unless ...),模型定义权限的含义。 - 使用声明式控制器宏设置授权策略:
allow_unauthenticated_access、ensure_can_administer。
依赖项
添加 gem 之前先问:原生 Rails 能做到吗?在项目内写 50-150 行代码,是否比添加依赖更简单?通常不使用:Devise、Pundit、ViewComponent、RSpec、FactoryBot、Redis(Solid Queue/Cache/Cable 使用数据库)、服务对象、表单对象、装饰器、GraphQL、SPA 框架、Tailwind。
审查优先级
- 正确性与数据安全。
- 多租户/安全边界。
- 可维护性与可读性。
- 性能热点。
- 风格与润色。
务必指出
- 面向租户的流程中未限定作用域的记录查询(
Comment.find(params[:id]))。 - 缺乏充分理由的新依赖项。
- 应在 SQL 中进行、却在内存中完成的筛选/排序(以及明明可以使用
.pluck(:name)却使用.map(&:name)的情况)。 - 用服务对象替代简单直接的模型方法。
- 资源建模更清晰时,却使用了非 RESTful 的自定义操作。
- 可以通过记录捕获操作人/时间,却使用了布尔状态列。
- 使用 HTTP 缓存(
fresh_when/etag)的表单页面——过期的 CSRF 令牌会导致 422 错误。 - 有谓词式 API 可用(StringInquirer / 字符串枚举)时,却使用字符串状态检查(
status == "x")。 - 使用
validates :x, uniqueness: true,却没有对应的唯一索引。 - 辅助方法依赖隐式实例变量,而不是显式参数。
- 将未转义的插值内容放入
html_safe字符串——先转义:"<b>#{h(input)}</b>".html_safe。 - 只针对 2-3 种情况使用元编程——直接编写方法即可。
- 仅在私有范围内使用的关注点——将其内联。
审查输出
- 从严重程度最高的问题开始。
- 对每个问题说明:问题、影响、具体修复方式,并附上文件:行号引用。
- 直接务实;“这是过度设计。”本身就是完整的句子。
- 结尾使用
Ship it或简短的优先级修复清单。
评论
支持 Markdown