mcpField
R
rails-conventions

rails-conventions

github.com/majesticlabs-dev开发5047 次安装7 周前更新
代码仓库

关于 rails-conventions

这是一套面向 Rails 应用开发与维护的代码和测试约定,强调简单、易测试的结构,并涵盖控制器、服务对象、Turbo Streams、JavaScript 导入和性能。

主要功能

  1. 01控制器与模型职责划分建议
  2. 02服务对象提取规则
  3. 03内联 Turbo Streams 模式
  4. 04基于 Minitest 与 fixtures 的测试
  5. 05使用 importmap 管理 JavaScript 导入
  6. 06性能与命名实践

适用场景

  1. 01组织 Rails 控制器与业务逻辑
  2. 02判断何时提取服务对象
  3. 03使用 fixtures 和外部依赖桩测试行为
  4. 04区分公共与管理端 JavaScript 依赖

安装

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

$npx skills add https://github.com/majesticlabs-dev/majestic-marketplace --skill rails-conventions

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

详细介绍

Rails 约定

注重实践的 Rails 模式,助力编写整洁、易维护的代码。

核心理念

  1. 重复 > 复杂:简单的重复代码胜过复杂的 DRY 抽象

    • “我宁愿有四个简单的控制器,也不愿有三个复杂的控制器”
  2. 可测试性 = 质量:如果代码难以测试,就需要重构其结构

  3. 增加控制器永远不是坏事。让控制器变复杂才是坏事。

Turbo Streams

简单的 Turbo Streams 必须直接写在控制器中:

# FAIL: Separate .turbo_stream.erb files for simple operations
render "posts/update"

# PASS: Inline array
render turbo_stream: [
  turbo_stream.replace("post_#{@post.id}", partial: "posts/post", locals: { post: @post }),
  turbo_stream.remove("flash")
]

控制器与 Concerns

业务逻辑应放在模型或 Concern 中,而不是控制器中。

# Concern structure
module Dispatchable
  extend ActiveSupport::Concern

  included do
    scope :available, -> { where(status: "pending") }
  end

  class_methods do
    def claim!(batch_size)
      # class-level behavior
    end
  end
end

提取服务

遇到以下多项情况时进行提取:

  • 复杂的业务规则(不只是“代码很长”)
  • 协调多个模型
  • 与外部 API 交互
  • 可跨控制器复用的逻辑

服务结构:

  • 只有一个公共方法
  • 按职责命名空间组织(Extraction::RegexExtractor)
  • 构造函数接收依赖项
  • 返回数据结构,而不是领域对象

现代 Ruby 风格

# Hash shorthand
{ id:, slug:, doc_type: kind }

# Safe navigation
created_at&.iso8601
@setting ||= SlugSetting.active.find_by!(slug:)

# Keyword arguments
def extract(document_type:, subject:, filename:)
def process!(strategy: nil)

枚举模式

# Frozen arrays with validation
STATUSES = %w[processed needs_review].freeze
enum :status, STATUSES.index_by(&:itself), validate: true

Scope 模式

# Guard with .present?, chainable design
scope :by_slug, ->(slug) { where(slug:) if slug.present? }
scope :from_date, ->(date) { where(created_at: Date.parse(date).beginning_of_day..) if date.present? }

def self.filtered(params)
  all.by_slug(params[:slug]).by_kind(params[:kind])
rescue ArgumentError
  all
end

错误处理

# Domain-specific errors
class InactiveSlug < StandardError; end

# Log with context, re-raise for upstream
def handle_exception!(error:)
  log_error("Exception #{error.class}: #{error.message}", error:)
  mark_failed!(error.message)
  raise
end

测试(Minitest + Fixtures)

test "describes expected behavior" do
  email = emails(:two)
  email.process
  email.reload
  assert_equal "finished", email.processing_status
end

原则:

  • 行为驱动:测试做什么,而不是如何实现
  • 基于 Fixture:使用 emails(:two) 进行设置
  • 模拟外部依赖:对 S3、API、PDF 使用存根
  • 状态验证:操作后调用 .reload
  • 辅助方法:build_valid_email、with_stubbed_download

命名(5 秒规则)

如果你无法在 5 秒内理解:

# FAIL
show_in_frame
process_stuff

# PASS
fact_check_modal
_fact_frame

JavaScript 与 Importmap

Rails 7+ 使用 importmap 管理 JS 依赖。将依赖限定在范围最小的入口点中。

多个入口点
# config/importmap.rb
pin "application"                    # Public entrypoint
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"

# Admin-only dependencies - pinned but only imported in admin entrypoint
pin "chartkick", to: "chartkick.js"
pin "Chart.bundle", to: "Chart.bundle.js"
// app/javascript/application.js — public pages only
import "@hotwired/turbo-rails"
import "@hotwired/stimulus"
import "controllers"

// app/javascript/admin.js — imports public base + admin-only libs
import "application"
import "chartkick"
import "Chart.bundle"
<%# Admin layout %>
<%= javascript_importmap_tags("admin") %>

<%# All other layouts %>
<%= javascript_importmap_tags %>
添加新的 JS 依赖
  1. 确定适用范围:面向公众还是仅限管理端?
  2. 在 config/importmap.rb 中 pin
  3. 在正确的入口点中导入
  4. 绝不要将仅限管理端的库添加到 application.js

性能

  • 考虑规模带来的影响
  • 不要过早缓存
  • KISS——保持简单
  • 索引会拖慢写入,只在需要时添加

评论

支持 Markdown