SuperAPI AI 工具集

rails-controller

编程与开发

查看项目

更新于 10-16 03:57

技能介绍

用途

rails-controller 用于以测试驱动开发(TDD)的方式创建 Rails 控制器。其核心原则是先编写 request spec,再编写实现代码,即 request spec first, then implementation。当你需要创建新控制器或为控制器添加动作时,可使用此技能。

主要能力

  • 按照 RESTful 风格生成控制器,遵循项目既有约定。
  • 采用 TDD 工作流:先写失败的 request spec(RED),运行确认失败,再实现控制器动作(GREEN),必要时重构。
  • 内置项目约定:使用 Pundit 进行授权(authorize @resource、policy_scope(Model));使用 Pagy 进行分页;使用 Presenter 进行视图格式化;通过 current_account 实现多租户;使用 Turbo Stream 响应实现动态更新。
  • 覆盖多租户隔离测试与授权测试(未授权访问返回 404)。
  • 支持命名空间控制器(如 settings/accounts)。
  • 支持 Turbo Stream 响应模式。

使用方式

TDD 工作流

  1. 在 spec/requests/ 中编写失败的 request spec(RED)。
  2. 运行 spec 确认 RED:bundle exec rspec spec/requests/[resources]_spec.rb
  3. 实现控制器动作(GREEN)。
  4. 再次运行 spec 确认 GREEN。
  5. 按需重构。

请求 Spec 示例

# spec/requests/[resources]_spec.rb
RSpec.describe "[Resources]", type: :request do
  let(:user) { create(:user) }
  let(:other_user) { create(:user) }

  before { sign_in user, scope: :user }

  describe "GET /[resources]" do
    let!(:resource) { create(:[resource], account: user.account) }
    let!(:other_resource) { create(:[resource], account: other_user.account) }

    it "returns http success" do
      get [resources]_path
      expect(response).to have_http_status(:success)
    end

    it "shows only current_user's resources (multi-tenant)" do
      get [resources]_path
      expect(response.body).to include(resource.name)
      expect(response.body).not_to include(other_resource.name)
    end
  end

  describe "GET /[resources]/:id" do
    let!(:resource) { create(:[resource], account: user.account) }

    it "returns http success" do
      get [resource]_path(resource)
      expect(response).to have_http_status(:success)
    end
  end

  describe "POST /[resources]" do
    let(:valid_params) { { [resource]: attributes_for(:[resource]) } }

    it "creates a new resource" do
      expect {
        post [resources]_path, params: valid_params
      }.to change([Resource], :count).by(1)
    end

    it "assigns to current_account" do
      post [resources]_path, params: valid_params
      expect([Resource].last.account).to eq(user.account)
    end
  end

  describe "authorization" do
    let!(:other_resource) { create(:[resource], account: other_user.account) }

    it "returns 404 for unauthorized access" do
      get [resource]_path(other_resource)
      expect(response).to have_http_status(:not_found)
    end
  end
end

控制器实现示例

# app/controllers/[resources]_controller.rb
class [Resources]Controller < ApplicationController
  before_action :set_[resource], only: [:show, :edit, :update, :destroy]

  def index
    authorize [Resource], :index?
    @pagy, resources = pagy(policy_scope([Resource]).order(created_at: :desc))
    @[resources] = resources.map { |r| [Resource]Presenter.new(r) }
  end

  def show
    authorize @[resource]
    @[resource] = [Resource]Presenter.new(@[resource])
  end

  def new
    @[resource] = current_account.[resources].build
    authorize @[resource]
  end

  def create
    @[resource] = current_account.[resources].build([resource]_params)
    authorize @[resource]

    if @[resource].save
      redirect_to [resources]_path, notice: "[Resource] created successfully"
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    authorize @[resource]
  end

  def update
    authorize @[resource]

    if @[resource].update([resource]_params)
      redirect_to @[resource], notice: "[Resource] updated successfully"
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    authorize @[resource]
    @[resource].destroy
    redirect_to [resources]_path, notice: "[Resource] deleted successfully"
  end

  private

  def set_[resource]
    @[resource] = policy_scope([Resource]).find(params[:id])
  end

  def [resource]_params
    params.require(:[resource]).permit(:name, :field1, :field2)
  end
end

命名空间控制器示例

对于 settings/accounts 这类嵌套路由:

# app/controllers/settings/accounts_controller.rb
module Settings
  class AccountsController < ApplicationController
    before_action :set_account

    def show
      authorize @account
    end

    private

    def set_account
      @account = current_account
    end
  end
end

Turbo Stream 响应模式

def create
  @resource = current_account.resources.build(resource_params)
  authorize @resource

  if @resource.save
    respond_to do |format|
      format.html { redirect_to resources_path, notice: "Created" }
      format.turbo_stream do
        flash.now[:notice] = "Created"
        @pagy, @resources = pagy(policy_scope(Resource).order(created_at: :desc))
        render turbo_stream: [
          turbo_stream.replace("resources-list", partial: "resources/list"),
          turbo_stream.update("modal", "")
        ]
      end
    end
  else
    render :new, status: :unprocessable_entity
  end
end

检查清单

  • 先编写 request spec(RED)
  • 测试多租户隔离
  • 测试授权(未授权返回 404)
  • 控制器每个动作使用 authorize
  • 控制器查询使用 policy_scope
  • 使用 Presenter 包装模型供视图使用
  • 定义强参数
  • 所有 spec 为 GREEN

适用场景

  • 创建新的 Rails 控制器。
  • 为已有控制器添加动作。
  • 需要遵循 Pundit 授权、Pagy 分页、Presenter 格式化、多租户与 Turbo Stream 约定的 Rails 项目。

限制

关于该技能的收费、价格、支持平台或其他未在资料中明确说明的事项,暂未提供。