mcpField
J
jr-rails-classic

jr-rails-classic

github.com/julianrubisch开发3046 次安装4 周前更新
代码仓库

关于 jr-rails-classic

jr-rails-classic 是面向 Rails 开发者的编码风格技能,依据 37signals 惯例指导应用实现。它强调富领域模型、REST 资源设计,以及使用 Minitest 和 fixtures 测试。

主要功能

  1. 01按生成器优先的开发流程
  2. 02富领域模型与薄控制器
  3. 03以子资源映射状态变更
  4. 04Minitest 与 fixtures 测试
  5. 05数据库约束与作业设计建议

适用场景

  1. 01按 37signals 惯例构建 Rails 功能
  2. 02规划 REST 控制器与数据模型
  3. 03为 Rails 应用设计测试和后台作业

安装

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

$npx skills add https://github.com/julianrubisch/skills --skill jr-rails-classic

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

包含的文件

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

reference/
agentic-worktrees.md
anti-patterns.md
coding-classic.md
coding-phlex.md
devops.md
patterns.md
reek.yml
reference/coding/
api.md
reference/refactorings/
000-template.md
001-extract-scope-from-controller.md
002-replace-conditional-with-polymorphism.md
003-replace-conditional-with-null-object.md
004-introduce-form-object.md
005-replace-subclasses-with-strategies.md
006-replace-mixin-with-composition.md
007-extract-validator.md
008-introduce-parameter-object.md
009-replace-callback-with-method.md
010-refactor-service-object-into-poro.md
extraction-signals.md
reference/shared/
architecture.md
authorization.md
callbacks.md
components.md
concerns.md
configuration.md
current_attributes.md
graphql.md
hotwire.md
instrumentation.md

详细介绍

Rails 经典编码风格

按照 37signals 的约定编写 Rails 应用代码。丰富的领域模型、CRUD 控制器、所有内容都由数据库支撑,使用 Minitest 和 fixtures。

核心工作流

  1. 先使用生成器 — rails g model、rails g controller、 rails g migration。生成器一次性生成正确的文件结构、测试桩 和路由条目。
  2. 实现顺序 — 模型 → 控制器 → 视图 → 测试。
  3. 发布、验证、改进 — 从原型走向生产环境,从真实使用中学习。
  4. 让它崩溃 — 使用带 bang 的方法,让 Rails 通过 422 响应处理 RecordInvalid。

防护原则

  • 不使用 service object — 使用命名空间位于 app/models/ 下的领域模型
  • 不添加自定义控制器操作 — 改为创建新资源(REST 映射)
  • 不使用 RSpec 或 factory_bot — 仅使用 Minitest 和 fixtures
  • 不使用 Redis — 使用 Solid Queue、Solid Cache、Solid Cable
  • 视图中不使用 strftime — 在初始化文件中自定义 DATE_FORMATS
  • 回调仅用于派生数据和异步分发 — 绝不用作业务逻辑
  • 对硬性保证,优先使用数据库约束而不是模型验证
  • 向任务传递 ID,而不是对象

约定速查

命名
  • 状态变更使用动词:card.close、board.publish
  • 根据记录是否存在使用谓词:card.closed?、card.golden?
  • Concern 使用形容词:Closeable、Publishable、Watchable
  • 控制器使用名词:Cards::ClosuresController
  • 作用域使用业务术语:scope :active、scope :chronologically
REST 映射

不使用自定义操作。创建子资源:

操作路由
关闭卡片POST /cards/:id/closure
归档卡片POST /cards/:id/archival
关注看板POST /boards/:id/watching
模型 — 将状态作为记录

不要使用布尔值,而是创建状态记录(自动获得时间戳和作者信息):

class Card::Closure < ApplicationRecord
  belongs_to :card
  belongs_to :creator, class_name: "User"
end

module Closeable
  extend ActiveSupport::Concern

  included do
    has_one :closure, dependent: :destroy
  end

  def closed? = closure.present?
  def close(creator: Current.user) = create_closure!(creator: creator)
  def reopen = closure&.destroy
end

# Querying
Card.joins(:closure)          # closed
Card.where.missing(:closure)  # open
Concern — 横向行为

自包含(关联 + 作用域 + 方法)。50–150 行。根据功能命名, 而非组织结构。

class Card < ApplicationRecord
  include Assignable, Closeable, Golden, Watchable, Searchable
end
模型命名空间下的 PORO

不适合放入 Concern 的业务逻辑:

# app/models/event/description.rb
class Event::Description
  def initialize(event) = @event = event
  def to_s = # ...
end
验证

模型上保持最少验证(数据完整性),在表单对象上进行情境化验证(UI 流程):

# Model — minimal
class User < ApplicationRecord
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  normalizes :email, with: ->(e) { e.strip.downcase }
end

# Migration — hard constraints
add_index :users, :email, unique: true
add_foreign_key :cards, :boards
关联设计
不要使用优先使用适用场景
has_and_belongs_to_manyhas_many :through联结模型需要属性或回调
1:NM:N(has_many :through)关系可能扩展
polymorphic: truedelegated_type需要类型安全且架构不同的变体

始终为 has_many/has_one 设置 :dependent。Rails 无法推断时使用 :inverse_of。

Rails 7.1+ 模式
# Delegated types
class Message < ApplicationRecord
  delegated_type :messageable, types: %w[Comment Reply Announcement]
end

# store_accessor for JSON/JSONB
class User < ApplicationRecord
  store_accessor :settings, :theme, :notifications_enabled
end
数据库
# UUIDs (UUIDv7 — time-sortable)
create_table :cards, id: :uuid do |t|
  t.references :board, type: :uuid, foreign_key: true
end

# Counter caches
class Comment < ApplicationRecord
  belongs_to :card, counter_cache: true
end

# Default values with Current
class Card < ApplicationRecord
  belongs_to :creator, class_name: "User", default: -> { Current.user }
end
控制器

保持精简。使用 Concern 共享行为:

module CardScoped
  extend ActiveSupport::Concern
  included { before_action :set_card }

  private
    def set_card
      @card  = Card.find(params[:card_id])
      @board = @card.board
    end
    def render_card_replacement
      render turbo_stream: turbo_stream.replace(@card)
    end
end

class Cards::ClosuresController < ApplicationController
  include CardScoped
  def create  = @card.close   && render_card_replacement
  def destroy = @card.reopen  && render_card_replacement
end
当前属性
class Current < ActiveSupport::CurrentAttributes
  attribute :session, :user, :account, :request_id
  delegate :user, to: :session, allow_nil: true
end
后台任务

保持精简的包装器 — 逻辑放在模型上:

module Watchable
  def notify_watchers_later = NotifyWatchersJob.perform_later(self)
  def notify_watchers
    watchers.each { |w| WatcherMailer.notification(w, self).deliver_later }
  end
end

class NotifyWatchersJob < ApplicationJob
  def perform(card) = card.notify_watchers
end
视图辅助方法

通过初始化文件格式化日期(视图中绝不使用 strftime):

# config/initializers/date_formats.rb
Date::DATE_FORMATS[:default]    = "%d.%m.%Y"
Time::DATE_FORMATS[:default]    = "%d.%m.%Y %H:%M"
Time::DATE_FORMATS[:time_only]  = "%H:%M"

使用 active_link_to gem 设置导航链接的激活状态。

测试
  • 使用 Minitest 和 fixtures,控制器使用集成测试
  • 测试可观察到的行为,而非实现细节
  • 能实际测试的内容就不要模拟
  • 外部 API 使用 VCR
首选技术栈
关注点Gem
前端turbo-rails、stimulus-rails、importmap-rails
资源propshaft
任务Solid Queue
缓存/线缆Solid Cache、Solid Cable
授权Pundit
部署Kamal + Thruster
Gem 选择
  1. 原生 Rails 能做到吗?
  2. 这是应用的核心关注点吗?如果是,就自己维护代码
  3. 它会增加基础设施吗?是否存在由数据库支撑的替代方案?
  4. 复杂度值得吗?
  5. 它来自你信任的人吗?

深入参考文件

当任务需要更深入的指导时,请按需阅读以下文件:

对于前端模式(Stimulus 控制器、Turbo Frames/Streams),请同时调用相关的 hwc-* skill。

评论

支持 Markdown