我有一个控制器“destroy”方法,用于检查各种依赖对象,并会失败并显示相应的警告消息。数据库端也会阻止删除,has_many :invoices, dependent: :restrict_with_error
但对于用户来说,删除失败是无声的。
Rubocop 认为“分配分支条件大小太高”,我也想重构它,因为它看起来有点重复。但是,我对返回值感到困惑。当我将检查移到私有函数中时,返回值会从函数中返回,但我还需要它来结束“destroy”方法。我有点需要双重返回。
def destroy
unless Current.user.default_entity.owner == Current.user ||
Current.user.default_entity.user_privileged?('manage_contacts')
redirect_back(fallback_location: contacts_path, alert: t('errors.no_access_rights')) and return
end
unless @contact.invoices.count.zero?
redirect_back(fallback_location: contacts_path,
alert: t('errors.cant_delete_contact_with_invoices')) and return
end
unless @contact.offers.count.zero?
redirect_back(fallback_location: contacts_path,
alert: t('errors.cant_delete_contact_with_offers')) and return
end
@contact.destroy
redirect_to contacts_path, status: :see_other
end
在我的应用程序中,其他控制器中也有类似的方法。是否有重构此类方法的模式?
总是有很多方法(尤其是使用 ruby 时)。这是我的方法以及我认为可以改进的地方,但您的意见和偏好可能有所不同。此外,您可以决定忽略 Rubocop。此警告并不是说这是错误的代码,只是说也许可以写得更好。
原始代码
变体 1
让我们摆脱重复 - 我们可以在出现问题时设置警报,如果我们设置了警报,我们就返回并重定向。
我们实际上并没有使代码更短或更少分支,但我们获得了一些见解 - 有查找可能问题的逻辑,并且此方法有两种结束方式 - 出现错误时进行 redirect_back,或者破坏联系。
变体 2
让我们将设置警报移至单独的私有方法,使操作更加简洁
这里的命名可能不是很好,但这只是快速的头脑风暴。
这再次增强了核心
destroy
方法的可读性,并且使得移动细节变得更加简单,从而导致:变体 3
为什么我们要检查模型是否可以删除?控制器不需要知道这一点,这些知识可能存在于模型中。此外,ActiveRecord 模型有一种很好的原生方式来管理验证和错误,因此这将帮助我们制作更标准的 Rails 端点。此处供参考 - Rails 文档和Rails 指南
漂亮又干净。
我并没有真正测试过所有的代码,所以我不能保证它能完全按照这种方式工作,我只是想指出方向
一些一般的想法:
Rubucop 的抱怨是正确的。循环复杂性实际上表明此代码存在一些问题。一般来说,如果您的方法包含多个显式返回,请停止并重做。
1. 授权很重要。请妥善对待授权。
像这样进行“内联”授权是一个非常糟糕的主意:
虽然最好的解决方案是添加一个授权层(例如 Pundit),但您至少应该借鉴 Pundit/CanCan 的做法,在请求未获得授权时引发异常,以确保它能被保释并且任何进一步的回调都会停止。
作为额外的奖励,您可以在测试/规范中绕过 rescue_from 来断言/预期引发异常。
更好的方法是提取,
Current.user.default_entity.owner == Current.user || Current.user.default_entity.user_privileged?('manage_contacts')
这样您的控制器就不会对模型有太多了解。Pundit 使用 Policy 类来实现这一点,您可以使用 PORO 进行模拟。2. 你让事情变得比应有的更难
不要仅仅假设它
@contact.destroy
会成功,而要检查返回值。如果销毁失败,这些restrict_with
错误关联将填充错误对象。就像创建/编辑对象时一样,您可以使用它们向用户提供反馈。不同之处在于,您想将它们推入闪存中(“警报”实际上只是闪存中的一个键),而不是将它们显示在表单上。
或者,你可以使用数组来代替闪存消息的不稳定字符串连接。
然而,这将需要对布局进行一些更改以处理显示数组闪存消息,我将其留给读者。
您可以使用 before_action 或辅助方法重构 destroy 方法,该方法返回一个布尔值,指示是否应继续删除。您可以使用一个保护子句方法,如果删除被阻止,则返回 true,并在其中执行重定向。