关于 rails-dev
面向 Rails 开发的技能,为编码 Agent 提供项目约定,并要求在处理各领域代码前阅读对应参考文档。内容涵盖模型、CRUD、测试、数据库约束和 Ruby 编码风格。
主要功能
- 01丰富模型承载领域逻辑
- 02以 CRUD 资源组织操作
- 03由数据库约束保证不变量
- 04按领域读取参考文档
- 05Ruby 风格与错误处理规范
适用场景
- 01设计 Rails 模型与控制器
- 02制定迁移和路由约定
- 03检查测试与错误处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tech-leads-club/ agent-skills --skill rails-dev安装后在 Claude Code 中输入/rails-dev确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api.mdasync.mdauth.mdcaching.mdconcerns.mdcrud.mderror-handling.mdevents.mdhttp-client.mdjobs.mdlogging.mdmailer.mdmigration.mdmodel.mdmulti-tenant.mdreview.mdroutes.mdstate-records.mdstimulus.mdtest.mdturbo.mdvalidator.mdview.mdwebhooks.md详细介绍
Rails 约定
核心理念
- 丰富的模型 - 业务逻辑放在模型中,而不是服务对象中
- 一切皆为 CRUD - 优先新增资源,而非新增动作(用
resource :closure,而不是post :close) - 以记录表示状态 - 使用
Closure模型,而不是closed: boolean - 通过 Concern 组合行为 -
Closeable、Watchable、Commentable - 明确胜于巧妙 - 在确实出现重复之前,先内联实现;抽象必须有充分理由,不能凭空臆造
- 接口保持精简 - 没有调用方的公共方法一律不留
- 让它崩溃 - 使用带感叹号的方法(
create!),在边界处处理失败,而不是预先防护(references/error-handling.md) - 在架构中定义不变量 - 硬性规则(存在性、唯一性、取值范围)应使用 NOT NULL / 唯一性 / 检查约束;验证用于向用户展示消息,而非作为事实来源。引用关系保持软性:不使用外键,在模型层保证完整性
- 尽量减少依赖 - 优先自行实现,再考虑使用 gem。不使用 Devise、Pundit、RSpec、FactoryBot、ViewComponent、服务对象/表单对象或装饰器
- 基于数据库 - 使用 Solid Queue/Cache/Cable,不使用 Redis
- 测试覆盖 - 维持大致 1:1 的测试比例(每行代码对应一行测试)
参考资料选择
此表是索引,不是具体内容。约定以参考文件为准,而不是以此表、代码库或一般 Rails 知识为准。请根据任务匹配下方条目,并在设计、编写、审查或分析代码之前,从头到尾阅读对应文件。阅读表格条目不等于阅读参考资料;根据代码库猜测,正是导致错误约定被采用的原因。
| 任务 | 参考资料 |
|---|---|
| 模型、验证、关联、业务逻辑 | references/model.md |
| 自定义验证器、多个模型共用的验证规则 | references/validator.md |
| 错误处理、救援边界、报告、重试 | references/error-handling.md |
| 控制器、CRUD 动作 | references/crud.md |
路由、config/routes.rb、资源映射 | references/routes.md |
| Concern、共享行为 | references/concerns.md |
| 状态跟踪(而非布尔值) | references/state-records.md |
| 身份验证、授权、会话、IDOR 范围限定 | references/auth.md |
| 数据库迁移 | references/migration.md |
| Minitest、固定数据、测试 | references/test.md |
| 视图、ERB、局部模板、辅助方法、展示逻辑 | references/view.md |
| Turbo Frames、Turbo Streams、实时功能 | references/turbo.md |
| Stimulus 控制器、少量 JS | references/stimulus.md |
| 后台作业、Solid Queue | references/jobs.md |
| 并发、Fiber、Async、外部 I/O | references/async.md |
| Mailer、电子邮件通知 | references/mailer.md |
| 片段缓存、HTTP 缓存 | references/caching.md |
| REST API、JSON 响应 | references/api.md |
| 多租户、账户范围限定 | references/multi-tenant.md |
| 事件跟踪、活动日志 | references/events.md |
| Webhook(入站/出站)、收件箱、幂等性 | references/webhooks.md |
| 代码审查、一致性检查 | references/review.md |
| HTTP 客户端、外部 API、Faraday | references/http-client.md |
| 日志记录、日志消息、Rails.logger | references/logging.md |
编码风格
以下是适用于各处的规则:无论哪个领域,都适用于每个文件。它们不能替代参考资料。对于任何特定领域(模型、控制器、作业、视图、测试等),在编写或审查代码之前,必须始终先加载上表中对应的参考资料。
条件语句
优先使用展开的 if/else,而不是返回值的守卫子句。若方法主体并不简单,在方法开头使用守卫子句是可以的。
# Don't: value-returning guard clause for a simple branch
def status_label
return "closed" if closed?
"open"
end
# Do: expanded if/else
def status_label
if closed?
"closed"
else
"open"
end
end
方法顺序
先是类方法,然后是公共方法(initialize 放在首位),最后是私有方法。按照调用顺序垂直排列方法:调用方放在被调用方上方。
# Don't: callee above its caller, public after private
class Signup
def create_member = Member.create!(email:)
def call = create_member
end
# Do: class method, then the caller, then its callees
class Signup
def self.call(...) = new(...).call
def call = create_member
private
def create_member = Member.create!(email:)
end
可见性
private 后不要留空行;其下的方法应缩进。仅包含私有方法的模块,应在顶部标记 private,并在其后留一个空行,方法不缩进。
# Don't: blank line after private, methods not indented
class Card
private
def closure_exists? = closure.present?
end
# Do: no blank line, indented under private
class Card
private
def closure_exists? = closure.present?
end
Bang 方法
仅当存在不带 bang 的对应方法时才使用 !(如 save/save!)。不要仅仅为了标记破坏性或重要操作而添加 !。
# Don't: unpaired bang used to signal "this is destructive"
def revoke! = update!(revoked_at: Time.current)
# Do: plain domain verb; the ! belongs to the paired persistence call
def revoke = update!(revoked_at: Time.current)
方法命名
公开方法应使用领域动词。当动词与作用域、谓词或核心方法(Kernel#fail)冲突时,添加 mark_ 前缀(不加 bang):mark_failed。类和概念的命名规范见 references/model.md。
# Don't: mark_ prefix when the plain verb is free
def mark_published = update!(published_at: Time.current)
# Do: plain verb; reserve mark_ for a real collision (fail -> Kernel#fail)
def publish = update!(published_at: Time.current)
def mark_failed = update!(failed_at: Time.current)
注释
默认不写注释。大多数注释都是噪音:代码和命名应能传达含义。
只有在代码无法表达清楚时才添加注释:
- 路径或代码无法清楚说明问题。
- 模型所用的语言含义模糊,注释可以说明它在领域中的位置。
不要:
- 重述代码已经表达的内容。
- 给代码分段(例如测试中的
# ----- some_method -----标题)。
当注释确有必要时:
- 始终使用简明的英文。
- 简洁。
- 不使用 LLM 式废话词汇。
- 如果列表比文字更清晰,就使用列表。
# Don't: restates what the code already says; section banner
# increment the attempts counter
attempts += 1
# ----- private helpers -----
# Do: explains the non-obvious why
# Circle returns 422 on duplicate emails, so we reconcile instead of recreating.
reconcile_member(email)
错误
不要吞掉错误。应让调用方能够获取失败信息,通常可以将错误记录在返回对象上,而不是记录日志后返回 false。完整的处理规范(记录还是抛出、报告、包装、重试)见 references/error-handling.md。
# Don't: swallow the error; the caller can't tell it failed
def provision_circle
circle_client.create_community_member(...)
rescue Circle::AdminClient::ApiError
false
end
# Do: record the failure on the returned object
def provision_circle
circle_client.create_community_member(...)
circle_access.mark_successful(external_id: response.dig(:community_member, :id))
rescue Circle::AdminClient::ApiError => e
circle_access.mark_failed(error: e.message) # caller checks access.failed?
end
来源
这些规范参考了以下应用,并可能与之有所不同: