mcpField
R
rails-dev

rails-dev

github.com/tech-leads-club开发7.1k66 次安装2 天前更新
代码仓库

关于 rails-dev

面向 Rails 开发的技能,为编码 Agent 提供项目约定,并要求在处理各领域代码前阅读对应参考文档。内容涵盖模型、CRUD、测试、数据库约束和 Ruby 编码风格。

主要功能

  1. 01丰富模型承载领域逻辑
  2. 02以 CRUD 资源组织操作
  3. 03由数据库约束保证不变量
  4. 04按领域读取参考文档
  5. 05Ruby 风格与错误处理规范

适用场景

  1. 01设计 Rails 模型与控制器
  2. 02制定迁移和路由约定
  3. 03检查测试与错误处理

安装

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

$npx skills add https://github.com/tech-leads-club/agent-skills --skill rails-dev

安装后在 Claude Code 中输入/rails-dev确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
api.md
async.md
auth.md
caching.md
concerns.md
crud.md
error-handling.md
events.md
http-client.md
jobs.md
logging.md
mailer.md
migration.md
model.md
multi-tenant.md
review.md
routes.md
state-records.md
stimulus.md
test.md
turbo.md
validator.md
view.md
webhooks.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 控制器、少量 JSreferences/stimulus.md
后台作业、Solid Queuereferences/jobs.md
并发、Fiber、Async、外部 I/Oreferences/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、Faradayreferences/http-client.md
日志记录、日志消息、Rails.loggerreferences/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

来源

这些规范参考了以下应用,并可能与之有所不同:

评论

支持 Markdown