关于 controller-patterns
用于生成和审查 Ruby on Rails 控制器的技能,面向构建 RESTful 应用端点的开发者。内容涵盖授权、嵌套资源、状态转换、批量操作、参数处理和响应规范。
主要功能
- 01RESTful CRUD 控制器模式
- 02Pundit 授权指导
- 03嵌套资源与状态转换控制器
- 04批量操作模式
- 05强参数与状态码规范
适用场景
- 01创建标准 CRUD 操作控制器
- 02为资源操作添加授权
- 03实现审批或激活状态转换
- 04审查嵌套资源和批量操作控制器
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rolemodel/ rolemodel-skills --skill controller-patterns安装后在 Claude Code 中输入/controller-patterns确认已加载。
详细介绍
Rails 控制器模式
快速参考
何时使用此技能
- 生成新的 Rails 控制器
- 根据最佳实践审查现有控制器
- 实现 RESTful 操作(index、show、new、create、edit、update、destroy)
- 使用 Pundit 添加授权
- 处理嵌套资源
- 实现状态转换(提交、审批、激活)
- 对资源执行批量操作
核心模式一览
标准 CRUD 控制器:
class ResourcesController < ApplicationController
before_action :set_resource, only: %i[show edit update destroy]
def index
@resources = policy_scope(Resource)
end
def show; end
def new
@resource = authorize Resource.new
end
def create
@resource = authorize Resource.new(resource_params)
@resource.save ? redirect_to(@resource, notice: 'Successfully Created Resource') : render('new', status: :unprocessable_content)
end
def edit; end
def update
@resource.update(resource_params) ? redirect_to(@resource, notice: 'Successfully Updated Resource') : render('edit', status: :unprocessable_content)
end
def destroy
@resource.destroy
redirect_to resources_url, notice: 'Successfully Deleted Resource'
end
private
def set_resource
@resource = authorize Resource.find(params[:id])
end
def resource_params
params.expect(resource: %i[attr1 attr2])
end
end
命名空间状态控制器:
class Resources::StatesController < ApplicationController
before_action :set_resource
before_action :ensure_valid_state, only: :create
def create
@resource.activate!
redirect_to resources_path, notice: 'Resource activated.'
end
def destroy
@resource.deactivate!
redirect_to resources_path, notice: 'Resource deactivated.'
end
private
def set_resource
@resource = current_user.resources.find(params[:resource_id])
end
def ensure_valid_state
redirect_to(resources_path, alert: 'Invalid state') unless @resource.can_activate?
end
end
决策树
Need to add controller functionality?
│
├─ Standard CRUD operations (list, view, create, edit, delete)?
│ └─ Use: Standard RESTful Controller Pattern
│
├─ State transitions (submit, approve, activate, publish)?
│ └─ Use: Namespaced State Controller (create/destroy actions)
│
├─ Bulk operations (bulk submit, bulk delete)?
│ └─ Use: Namespaced Bulk Controller (create action only)
│
├─ Nested under a single parent resource?
│ └─ Use: Nested RESTful Controller Pattern
│
├─ Child resource that hangs off several different parents?
│ (comments, reports, duplications, attachments)
│ └─ Use: polymorphic-parent-resources skill
│ (one route concern + one controller, not one controller per parent)
│
└─ Complex authorization rules?
└─ Add: Policy scopes and explicit authorization checks
基本模式
1. 授权(Pundit)
模式: 使用 authorize 或 policy_scope 授权所有资源交互。
# Collections - use policy_scope
def index
@products = policy_scope(Product)
end
# New instances - authorize the class
def new
@product = authorize Product.new
end
# Existing instances - authorize in set method
def set_product
@product = authorize Product.find(params[:id])
end
规则:
- 对集合(index)使用
policy_scope() - 对新记录(new、create)使用
authorize ClassName.new() - 在
set_*方法中对现有记录调用authorize - 绝不要跳过资源操作的授权
2. 前置操作
模式: 使用明确的操作范围提取通用的设置逻辑。
# Resource loading (most common)
before_action :set_product, only: %i[show edit update destroy]
# Parent resource loading (nested)
before_action :set_company
before_action :set_employee, only: %i[show edit update destroy]
# State validation
before_action :ensure_pending, only: :create
before_action :ensure_stopped, only: :create
规则:
- 始终使用
only:或except: - 使用描述性名称:
set_[resource]、ensure_[state]、require_[permission] - 顺序很重要——按声明顺序执行
- 每个方法只负责一项职责
状态验证示例:
def ensure_pending
return if @resource.pending?
redirect_to resources_path, alert: 'Must be pending.'
end
3. RESTful 操作模式
Index - 列出所有资源:
def index
@resources = policy_scope(Resource)
end
Show - 显示单个资源:
def show
# Resource set via before_action
# Load scoped associations if needed
@related = policy_scope(@resource.related_items)
end
New - 新建资源的表单:
def new
@resource = authorize Resource.new
end
Create - 保存新资源:
def create
@resource = authorize Resource.new(resource_params)
if @resource.save
redirect_to @resource, notice: 'Successfully Created Resource'
else
render :new, status: :unprocessable_content
end
end
Edit - 编辑现有资源的表单:
def edit
# Resource set via before_action
end
Update - 保存对资源的更改:
def update
if @resource.update(resource_params)
redirect_to @resource, notice: 'Successfully Updated Resource'
else
render :edit, status: :unprocessable_content
end
end
Destroy - 删除资源:
def destroy
@resource.destroy
redirect_to resources_url, notice: 'Successfully Deleted Resource'
end
4. 强参数
模式: 在私有方法中定义允许的属性。
def resource_params
params.expect(
resource: [
:simple_attr,
:another_attr,
nested_attrs: %i[id attr1 attr2 _destroy],
array_attrs: [],
multiple_ids: []
]
)
end
规则:
- 使用
params.expect(model: [...]) - 嵌套属性:
{nested_attrs: %i[id attr _destroy]} - 数组:
{array_attr: []} - 对于更新,在嵌套属性中包含
:id;对于删除,包含_destroy
5. HTTP 状态码
# Success - redirects (default 302, no status needed)
redirect_to @resource, notice: 'Success'
# Validation failure - render with unprocessable_content
render :new, status: :unprocessable_content # 422
render :edit, status: :unprocessable_content # 422
# Other statuses (rare in controllers)
head :no_content # 204
head :forbidden # 403
head :not_found # 404
规则:
- 重定向无需显式指定状态码
- 验证失败:
:unprocessable_content(422) - Turbo 需要正确的状态码来处理错误
6. Flash 消息
模式: 提供一致、友好的用户提示。
# Success (notice:)
redirect_to @product, notice: 'Successfully Created Product'
redirect_to @product, notice: 'Successfully Updated Product'
redirect_to products_url, notice: 'Successfully Deleted Product'
# Errors (alert:)
redirect_to products_path, alert: 'Must be pending to submit.'
redirect_to products_path, alert: 'Cannot delete active product.'
规则:
- 格式:
Successfully [Action] [Resource] - 成功时使用
notice: - 出现错误或警告时使用
alert: - 保持简洁,并以操作为中心
- 资源名称使用标题式大小写
7. 命名约定
# Controllers
ProductsController < ApplicationController
Admin::ProductsController < Admin::BaseController
Products::SubmissionsController < ApplicationController
# Instance variables
@product, @user # Singular for one resource
@products, @users # Plural for collections
# Private methods
def set_product # Resource loading
def product_params # Strong parameters
def ensure_pending # State validation
def require_admin # Authorization check
完整示例
简单 CRUD 控制器
class ProductsController < ApplicationController
before_action :set_product, only: %i[show edit update destroy]
def index
@products = policy_scope(Product)
end
def show; end
def new
@product = authorize Product.new
end
def create
@product = authorize Product.new(product_params)
if @product.save
redirect_to @product, notice: 'Successfully Created Product'
else
render :new, status: :unprocessable_content
end
end
def edit; end
def update
if @product.update(product_params)
redirect_to @product, notice: 'Successfully Updated Product'
else
render :edit, status: :unprocessable_content
end
end
def destroy
@product.destroy
redirect_to products_url, notice: 'Successfully Deleted Product'
end
private
def set_product
@product = authorize Product.find(params[:id])
end
def product_params
params.expect(product: %i[name description price])
end
end
嵌套资源控制器
仅当子资源恰好只有一种父资源时使用此模式。如果子资源可以归属于多种不同的父资源(评论、报告、重复项、附件),则不要为每种父资源复制此模式——请改为阅读 polymorphic-parent-resources 技能。
class OrderItemsController < ApplicationController
before_action :set_order
before_action :set_order_item, only: %i[show edit update destroy]
def index
@order_items = policy_scope(@order.order_items)
end
def new
@order_item = authorize @order.order_items.build
end
def create
@order_item = authorize @order.order_items.build(order_item_params)
if @order_item.save
redirect_to [@order, @order_item], notice: 'Successfully Created Order Item'
else
render :new, status: :unprocessable_content
end
end
def update
if @order_item.update(order_item_params)
redirect_to [@order, @order_item], notice: 'Successfully Updated Order Item'
else
render :edit, status: :unprocessable_content
end
end
def destroy
@order_item.destroy
redirect_to order_order_items_url(@order), notice: 'Successfully Deleted Order Item'
end
private
def set_order
@order = authorize Order.find(params[:order_id])
end
def set_order_item
@order_item = authorize @order.order_items.find(params[:id])
end
def order_item_params
params.expect(order_item: %i[product_id quantity price])
end
end
常见错误
| ❌ 反面模式 | ✅ 正确模式 |
|---|---|
@product = Product.new(product_params) | @product = authorize Product.new(product_params) |
render :new, status: :unprocessable_entity | render :new, status: :unprocessable_content |
render :new(验证失败时) | render :new, status: :unprocessable_content |
@product = Product.new(params[:product]) | @product = Product.new(product_params) |
params.require(:product).permit(:name) | params.expect(product: %i[name]) |
redirect_to @product, notice: 'Product created!'redirect_to @product, notice: 'Success!' | redirect_to @product, notice: 'Successfully Created Product' |
before_action :set_product(未指定范围) | before_action :set_product, only: %i[show edit update destroy] |
| 为状态变更编写自定义操作 | 使用带命名空间的控制器和 RESTful 操作 |
为每个父资源创建一个带命名空间的控制器(Estimates::CommentsController、Widgets::CommentsController) | 为所有父资源使用同一个控制器——参见 polymorphic-parent-resources 技能 |
在父控制器中添加 add_comment / create_report 操作 | 使用单独的 RESTful 子控制器——参见 polymorphic-parent-resources 技能 |
高级模式
带命名空间的状态控制器
适用场景: 操作表示资源的状态转换(提交/撤回、激活/停用、批准/拒绝)。
模式: 在父资源下使用命名空间,并通过 create 和 destroy 处理状态变更。
# Controller: app/controllers/time_entries/submissions_controller.rb
class TimeEntries::SubmissionsController < ApplicationController
before_action :set_time_entry
before_action :ensure_valid_for_submission, only: :create
before_action :ensure_submitted, only: :destroy
def create
@time_entry.update!(status: :submitted, submitted_at: Time.current)
redirect_to time_entries_path, notice: 'Time entry submitted for approval.'
end
def destroy
@time_entry.update!(status: :pending, submitted_at: nil)
redirect_to time_entries_path, notice: 'Time entry unsubmitted.'
end
private
def set_time_entry
@time_entry = current_user.time_entries.find(params[:time_entry_id])
end
def ensure_valid_for_submission
return if @time_entry.pending? && @time_entry.stopped?
redirect_to time_entries_path, alert: 'Only stopped pending entries can be submitted.'
end
def ensure_submitted
return if @time_entry.submitted?
redirect_to time_entries_path, alert: 'Only submitted entries can be unsubmitted.'
end
end
# Routes
resources :time_entries do
resource :submission, only: [:create, :destroy], module: :time_entries
end
# Views
button_to time_entry_submission_path(@time_entry), method: :post # Submit
button_to time_entry_submission_path(@time_entry), method: :delete # Unsubmit
优点:
- 符合 RESTful 风格(使用标准的 create/destroy 操作)
- 文件组织清晰(controllers/time_entries/submissions_controller.rb)
- 将验证逻辑提取到 before_actions 中
- 一个控制器即可处理两种状态转换
- 易于测试
批量操作控制器
适用场景: 一次操作多条记录(批量提交、批量删除、批量归档)。
模式: 使用仅包含 create 操作的带命名空间控制器,并在 before_actions 中进行验证。
# Controller: app/controllers/time_entries/bulk_submissions_controller.rb
class TimeEntries::BulkSubmissionsController < ApplicationController
before_action :set_entries
before_action :ensure_entries_present
before_action :ensure_entries_valid
def create
@entries.update_all(status: TimeEntry.statuses[:submitted], submitted_at: Time.current)
redirect_to time_entries_path, notice: "#{@entries.count} #{'entry'.pluralize(@entries.count)} submitted."
end
private
def set_entries
ids = params[:time_entry_ids] || []
@entries = current_user.time_entries.where(id: ids)
end
def ensure_entries_present
return if @entries.any?
redirect_to time_entries_path, alert: 'No entries selected.'
end
def ensure_entries_valid
invalid = @entries.reject { |e| e.pending? && e.stopped? }
return if invalid.empty?
redirect_to time_entries_path, alert: 'Only stopped pending entries can be submitted.'
end
end
# Routes
resource :bulk_submissions, only: :create, module: :time_entries
# Views
form_with url: bulk_submissions_path, method: :post do |f|
# checkboxes for time_entry_ids[]
end
show 中的作用域集合
适用场景: 显示包含多个相关集合的资源。
def show
@active_projects = policy_scope(@company.projects.active)
@archived_projects = policy_scope(@company.projects.archived)
@team_members = policy_scope(@company.users)
end
复杂的嵌套属性
适用场景: 表单接受嵌套记录(has_many 关联)。
def product_params
params.expect(
product: [
:name,
:description,
:price,
images_attributes: %i[id url alt_text _destroy],
variants_attributes: %i[id sku price stock_count _destroy],
tags: [],
category_ids: []
]
)
end
要点:
- 更新现有嵌套记录时包含
:id - 通过嵌套属性删除记录时包含
_destroy - 简单数组使用
{ array_attr: [] } - 嵌套属性哈希使用
{ nested_attrs: %i[attr1 attr2] }
Agent 指令
生成新控制器
-
确定控制器类型:
- 标准 CRUD → 使用简单 CRUD 模式
- 状态转换 → 使用带命名空间的状态模式
- 批量操作 → 使用批量操作模式
- 嵌套资源仅隶属于一个父资源 → 使用嵌套资源模式
- 可能隶属于多个父资源的子资源 → 阅读
polymorphic-parent-resources技能
-
应用模式:
- 从合适的模板开始
- 添加授权(
authorize、policy_scope) - 定义强参数
- 添加范围明确的 before_actions
- 使用正确的状态码和 flash 消息
-
遵循约定:
- 命名:
ResourcesController或Resources::StatesController - 继承自:
ApplicationController - 实例变量:
@resource(单数)、@resources(复数) - 私有方法:
set_resource、resource_params
- 命名:
审查现有控制器
检查以下事项:
- 对所有资源操作进行授权
-
before_action使用only:/except: - 使用强参数(不直接访问
params[]) - 验证失败时使用状态
:unprocessable_content - flash 消息一致:
Successfully [Action] [Resource] - 遵循恰当的命名约定
- 在 before_actions 中验证状态(而非在主操作中)
- 不存在仅因加载的父资源不同而有所区别的同级控制器(参见
polymorphic-parent-resources)
优先级顺序: 安全性(授权、参数)→ RESTful 模式 → 状态码 → 消息