You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重构带有多种失败提示的控制器destroy方法

控制器Destroy方法重构方案及通用模式

一、当前代码的直接重构

针对你当前的destroy方法,核心问题是重复的跳转逻辑和过高的ABC指标,我们可以把校验逻辑拆分为独立的私有方法,每个方法仅负责判断并返回错误信息(无错误则返回nil),在主方法里统一处理跳转:

def destroy
  # 依次检查权限和关联记录,只要有错误就终止流程
  error_message = validate_destroy_permission || validate_no_associated_records
  if error_message
    redirect_back(fallback_location: contacts_path, alert: error_message)
    return
  end

  @contact.destroy
  redirect_to contacts_path, status: :see_other
end

private

# 校验删除权限
def validate_destroy_permission
  unless Current.user.default_entity.owner == Current.user ||
         Current.user.default_entity.user_privileged?('manage_contacts')
    t('errors.no_access_rights')
  end
end

# 校验关联记录是否为空
def validate_no_associated_records
  return t('errors.cant_delete_contact_with_invoices') unless @contact.invoices.count.zero?
  return t('errors.cant_delete_contact_with_offers') unless @contact.offers.count.zero?
  nil
end

这样既消除了重复的redirect_back代码,也把分支逻辑拆分到独立方法中,大幅降低ABC指标。

二、多控制器通用重构模式

因为你其他控制器也有类似逻辑,推荐以下两种通用方案:

1. 使用ActiveSupport Concern封装控制器通用逻辑

创建一个通用的关注点,封装销毁流程的骨架,子类控制器只需实现具体的校验规则:

# app/controllers/concerns/destroyable.rb
module Destroyable
  extend ActiveSupport::Concern

  included do
    private

    # 通用销毁流程:接收资源、成功路径、 fallback路径
    def destroy_resource(resource, success_path:, fallback_path:)
      error_message = validate_destroy_permission(resource) || validate_no_associated_records(resource)
      if error_message
        redirect_back(fallback_location: fallback_path, alert: error_message)
        return false
      end

      resource.destroy
      redirect_to success_path, status: :see_other
      true
    end

    # 子类必须实现:权限校验逻辑
    def validate_destroy_permission(resource)
      raise NotImplementedError, "请在控制器中实现#{__method__}方法"
    end

    # 子类必须实现:关联记录校验逻辑
    def validate_no_associated_records(resource)
      raise NotImplementedError, "请在控制器中实现#{__method__}方法"
    end
  end
end

在ContactsController中引入并实现具体逻辑:

class ContactsController < ApplicationController
  include Destroyable

  def destroy
    @contact = Contact.find(params[:id])
    destroy_resource(@contact, success_path: contacts_path, fallback_path: contacts_path)
  end

  private

  def validate_destroy_permission(_contact)
    unless Current.user.default_entity.owner == Current.user ||
           Current.user.default_entity.user_privileged?('manage_contacts')
      t('errors.no_access_rights')
    end
  end

  def validate_no_associated_records(contact)
    return t('errors.cant_delete_contact_with_invoices') unless contact.invoices.count.zero?
    return t('errors.cant_delete_contact_with_offers') unless contact.offers.count.zero?
    nil
  end
end

其他控制器只需复制这个模式,替换资源类型和校验规则即可。

2. 使用服务对象封装业务逻辑

把销毁的所有业务逻辑(权限、关联检查、执行销毁)移到独立的服务对象中,控制器仅负责请求转发和响应:

# app/services/contact_destroy_service.rb
class ContactDestroyService
  def initialize(user, contact)
    @user = user
    @contact = contact
  end

  # 返回格式:[是否成功, 错误信息]
  def call
    return [false, t('errors.no_access_rights')] unless permission_granted?
    return [false, t('errors.cant_delete_contact_with_invoices')] unless @contact.invoices.empty?
    return [false, t('errors.cant_delete_contact_with_offers')] unless @contact.offers.empty?

    @contact.destroy
    [true, nil]
  end

  private

  def permission_granted?
    @user.default_entity.owner == @user ||
      @user.default_entity.user_privileged?('manage_contacts')
  end
end

控制器中的destroy方法简化为:

def destroy
  @contact = Contact.find(params[:id])
  success, error_message = ContactDestroyService.new(Current.user, @contact).call

  if success
    redirect_to contacts_path, status: :see_other
  else
    redirect_back(fallback_location: contacts_path, alert: error_message)
  end
end

这种方式完全分离了业务逻辑和控制器的请求处理职责,其他控制器可以创建对应的XXDestroyService类,保持逻辑一致性。

补充:结合数据库层的restrict_with_error

你已经在数据库层用了dependent: :restrict_with_error,可以在销毁后检查模型的错误信息,作为兜底:

def destroy
  # ... 权限和关联检查 ...

  @contact.destroy
  if @contact.errors.present?
    redirect_back(fallback_location: contacts_path, alert: @contact.errors.full_messages.first)
    return
  end

  redirect_to contacts_path, status: :see_other
end

不过提前在业务层做检查,能给用户更友好的提示,避免数据库层抛出错误后再处理。

内容的提问来源于stack exchange,提问作者Ricky883249

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 23:45:05