更新于

给 Rails 项目写 Cursor Rules:从约定到自动化


Ruby on Rails 有一套极其强烈的约定——Convention over Configuration。这既是它的优势,也是让 AI 助手”写错”的高频原因:没有约定背景的 AI 倾向于生成”合法但不地道”的 Rails 代码,比如绕过 Active Record 回调直接写 SQL、用非 RESTful 路由命名、在 Controller 里堆业务逻辑。

一套针对 Rails 项目的 Cursor Rules,本质上是把 Rails Way 翻译成 AI 能理解的指令集。本文分享实战中积累的规则体系,按场景分层组织。

一、为什么 Rails 项目特别需要定制规则

一般框架的 AI 错误是语法层面的,容易被 linter 拦住。Rails 的 AI 错误更多是架构层面的

  • 在 Model 里写本该放 Service Object 的逻辑
  • find_by_sql 代替 scope
  • 忽略 Rails 的 before_action,直接在每个 action 里重复鉴权
  • 生成 migration 时不遵守 add_indexreferences 的惯用法
  • 忽视 Turbo/Hotwire,用 JavaScript 手写本可用 Turbo Frame 替代的交互

这些错误通过 RuboCop 也只能部分发现。所以需要在 Cursor Rules 层面就把 Rails 约定明确化。

二、文件组织:单文件还是多文件规则

Cursor 支持两种规则组织方式,详细说明可参考 .cursorrules 最佳实践。对 Rails 项目,推荐多文件结构

.cursor/
  rules/
    00-project-overview.md     # 项目背景、技术栈版本
    10-ruby-style.md           # Ruby 代码风格
    20-rails-conventions.md    # Rails 核心约定
    30-active-record.md        # Active Record 规则
    40-testing.md              # RSpec / Minitest 规则
    50-frontend.md             # Hotwire / ViewComponent 规则
    60-security.md             # 安全要求

每个文件 100–300 行,专注一个维度。Cursor 会根据你当前编辑的文件类型自动加载最相关的规则文件。

三、项目概览规则(00-project-overview.md)

这是所有规则的基座,每个 AI 对话都会加载:

# 项目背景
这是一个 Ruby on Rails 7.2 项目,使用 PostgreSQL 数据库。
前端使用 Hotwire(Turbo + Stimulus),无独立 SPA 框架。
部署在 Heroku,使用 Sidekiq 处理后台任务。

# 技术约束
- Ruby >= 3.2
- Rails >= 7.2
- PostgreSQL 16
- Sidekiq 7.x(后台任务,不使用 Delayed::Job)
- RSpec 3.x(测试框架,不使用 Minitest)
- ViewComponent(组件化 UI,不使用 Cells)

# 禁止引入的依赖
- 不引入 React / Vue / Angular
- 不使用 jQuery(已从项目移除)
- 不使用 Pundit(权限用自定义 Policy 类)

四、Rails 核心约定规则(20-rails-conventions.md)

这是最重要的规则文件,覆盖 MVC 分层和 RESTful 约定:

# Controller 约定
- Controller 只负责 HTTP 层:参数白名单、调用 Service、渲染响应
- 不在 Controller 里写业务逻辑,复杂逻辑提取到 app/services/ 或 app/commands/
- 始终使用 before_action 进行鉴权,不在每个 action 里重复
- 使用标准 7 个 RESTful action:index / show / new / create / edit / update / destroy
- 非 RESTful 操作考虑新建 Controller(如 Sessions::OauthController)

示例结构:
  def create
    @user = UserRegistrationService.call(user_params)
    if @user.persisted?
      redirect_to @user, notice: "注册成功"
    else
      render :new, status: :unprocessable_entity
    end
  end

# Model 约定
- Model 包含:关联关系、验证、scope、实例方法(面向对象行为)
- 不在 Model 里写跨 Model 的业务协调逻辑
- scope 优先于类方法,使用 lambda 语法:scope :active, -> { where(status: :active) }
- 回调(before_save 等)只用于"总是需要"的数据一致性操作,不用于触发邮件等副作用

# 路由约定
- 优先使用 resources / resource,嵌套不超过 2 层
- 自定义路由用 member / collection 块,不散落在 namespace 外
- 路由文件按模块分组,用 namespace 或 scope 区隔

五、Active Record 专项规则(30-active-record.md)

Active Record 是 Rails 最容易被 AI “写坏”的部分:

# 查询约定
- 使用 scope 和关联方法,避免裸写 SQL 字符串
- N+1 查询:凡涉及关联数据,必须使用 includes / eager_load / preload
- 不使用 find_by_sql,除非有明确性能测试证明必要
- 批量操作用 find_each(遍历大量记录)或 insert_all(批量插入)

# Migration 约定
- 列名遵循 Rails 惯例(外键用 _id 后缀,布尔值用 is_ 前缀不强制但需一致)
- 添加外键时同时添加索引:add_reference :orders, :user, foreign_key: true
- 不在 migration 里写业务逻辑或调用 Model 方法(Model 结构可能已变)
- 每个 migration 只做一件事,命名清晰:add_status_to_orders

# 校验
- 数据库层面加约束(null: false、uniqueness index),Model 层面写对应 validation
- 两层约束都要有,不能只依赖 Model validation

示例 migration:
  def change
    add_column :orders, :status, :string, null: false, default: "pending"
    add_index :orders, :status
  end

六、测试规则(40-testing.md)

Rails 项目测试往往是 Cursor 最难写对的部分,因为 RSpec 的 DSL 和 Rails helper 组合复杂:

# RSpec 基本约定
- 文件结构镜像 app/ 目录:app/models/user.rb → spec/models/user_spec.rb
- 用 describe 描述类/方法,用 context 描述场景,用 it 描述预期行为
- 每个 it 块只测一件事(One Expectation Per Test 不强制,但尽量)

# Factory vs Fixture
- 使用 FactoryBot,不使用 Rails fixtures
- Factory 定义在 spec/factories/ 下,一个文件对应一个 Model
- 用 create_list / build_list 创建批量数据,不循环 create

# 示例结构
  RSpec.describe Order, type: :model do
    describe "validations" do
      it "requires status" do
        order = build(:order, status: nil)
        expect(order).not_to be_valid
        expect(order.errors[:status]).to include("can't be blank")
      end
    end

    describe "#total_price" do
      context "when order has items" do
        it "returns sum of item prices" do
          order = create(:order, :with_items, item_count: 3)
          expect(order.total_price).to eq(order.items.sum(:price))
        end
      end
    end
  end

# 禁止
- 不在 spec 里直接调用 ActiveRecord 查询测试 UI 逻辑(用 request spec)
- 不 mock ActiveRecord 关联,用真实 DB(测试数据库)

七、Hotwire / 前端规则(50-frontend.md)

这是现代 Rails 项目最常被 AI”走偏”的地方——AI 容易自动引入 React/Vue,或用 JavaScript 实现本可用 Turbo 完成的交互:

# Turbo 优先原则
- 页面局部更新优先使用 Turbo Frame,不写手动 fetch + DOM 操作
- 表单提交使用 Turbo(Rails 7 默认),响应返回 Turbo Stream 或重定向
- 不使用 $.ajax 或 fetch 实现本可用 Turbo 替代的功能

# Stimulus 控制器
- 复杂 JS 交互才使用 Stimulus,轻交互(toggle、简单动画)用内联 data 属性
- 控制器放在 app/javascript/controllers/ 下
- 命名:功能描述 + _controller.js,如 dropdown_controller.js

# ViewComponent
- 可复用 UI 片段(如 Alert、Badge、Table Row)用 ViewComponent 封装
- 放在 app/components/ 下,对应 spec/components/ 写测试
- 不在 partial 里写复杂逻辑,复杂 partial 考虑迁移到 ViewComponent

# 禁止
- 不引入 React / Vue 解决局部更新问题,这是 Turbo 应该做的
- 不使用 Rails UJS(已废弃),只使用 Turbo

八、安全规则(60-security.md)

# SQL 注入防护
- 永远不用字符串插值拼 SQL:where("name = '#{params[:name]}'") 是错误的
- 使用参数化查询:where("name = ?", params[:name]) 或 where(name: params[:name])

# Mass Assignment
- 始终使用 strong parameters(params.require(:user).permit(...))
- 不使用 params[:user].to_unsafe_h

# XSS
- ERB 模板默认 HTML 转义,不使用 raw() 或 html_safe 除非有明确的安全验证
- 如果数据来自用户输入,不允许标记为 html_safe

# 认证与鉴权
- before_action :authenticate_user! 放在 ApplicationController,子 Controller 用 skip_before_action
- 不在 action 里手动判断 current_user,抽象到 Policy 类

九、团队协作:让规则落地

规则写好只是开始,更重要的是让团队真正用起来:

1. 把规则文件纳入 Git 版本控制

git add .cursor/rules/
git commit -m "chore: 添加 Cursor Rules for Rails 项目"

2. 在 PR 模板里加一行检查

- [ ] 是否遵循了 .cursor/rules/ 中的约定(AI 生成代码请特别检查)

3. 定期回顾和迭代

每两周的迭代会议里,花 15 分钟讨论”AI 生成了什么不符合规范的代码”,然后更新对应的规则文件。规则应该是活的文档,随项目演化而迭代。

关于规则在不同项目中的演化规律,可参考 Cursor Rules 在真实项目中的演化

十、完整规则模板下载

把本文的所有规则整合成一个可用的模板目录,初始化方式:

mkdir -p .cursor/rules

# 创建完整规则集(把本文各节内容复制进去)
touch .cursor/rules/00-project-overview.md
touch .cursor/rules/10-ruby-style.md
touch .cursor/rules/20-rails-conventions.md
touch .cursor/rules/30-active-record.md
touch .cursor/rules/40-testing.md
touch .cursor/rules/50-frontend.md
touch .cursor/rules/60-security.md

# 纳入版本控制
echo ".cursor/rules/" >> .gitignore-remove  # 确保不在 .gitignore 里
git add .cursor/rules/

初始化后,在每个规则文件里填入适合你项目的具体内容。项目背景、禁用依赖、团队约定这些是最值得花时间写清楚的部分——AI 不知道的信息,规则文件帮你补全。

相关阅读

在 Rails 项目里配好 Cursor Rules 后,AI 生成的代码质量会有明显提升。如果你的团队同时使用多种 AI 模型(如 Claude、GPT-4o),YoTradeApi 提供统一的 API 中转端点,可直接在 Cursor 自定义模型配置中使用,按需切换模型而不改动代码。