85

在销毁一个宁静的资源时,我想在允许销毁操作继续之前保证一些事情?基本上,如果我注意到这样做会使数据库处于无效状态,我希望能够停止销毁操作?销毁操作没有验证回调,那么如何“验证”是否应该接受销毁操作?

4

11 回答 11

74

您可以引发一个异常,然后捕获该异常。Rails 将删除包装在事务中,这有助于解决问题。

例如:

class Booking < ActiveRecord::Base
  has_many   :booking_payments
  ....
  def destroy
    raise "Cannot delete booking with payments" unless booking_payments.count == 0
    # ... ok, go ahead and destroy
    super
  end
end

或者,您可以使用 before_destroy 回调。此回调通常用于销毁依赖记录,但您可以抛出异常或添加错误。

def before_destroy
  return true if booking_payments.count == 0
  errors.add :base, "Cannot delete booking with payments"
  # or errors.add_to_base in Rails 2
  false
  # Rails 5
  throw(:abort)
end

myBooking.destroy现在将返回 false,并将myBooking.errors在返回时填充。

于 2008-09-23T19:22:37.723 回答
49

只是一个说明:

用于导轨 3

class Booking < ActiveRecord::Base

before_destroy :booking_with_payments?

private

def booking_with_payments?
        errors.add(:base, "Cannot delete booking with payments") unless booking_payments.count == 0

        errors.blank? #return false, to not destroy the element, otherwise, it will delete.
end
于 2011-07-29T13:54:27.240 回答
20

这就是我对 Rails 5 所做的:

before_destroy do
  cannot_delete_with_qrcodes
  throw(:abort) if errors.present?
end

def cannot_delete_with_qrcodes
  errors.add(:base, 'Cannot delete shop with qrcodes') if qrcodes.any?
end
于 2016-06-17T11:36:05.277 回答
15

Rails 6 的情况:

这有效:

before_destroy :ensure_something, prepend: true do
  throw(:abort) if errors.present?
end

private

def ensure_something
  errors.add(:field, "This isn't a good idea..") if something_bad
end

validate :validate_test, on: :destroy不起作用:https ://github.com/rails/rails/issues/32376

Since Rails 5 throw(:abort) is required to cancel execution: https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chain

prepend: true is required so that dependent: :destroy doesn't run before the validations are executed: https://github.com/rails/rails/issues/3458

You can fish this together from other answers and comments, but I found none of them to be complete.

As a sidenote, many used a has_many relation as an example where they want to make sure not to delete any records if it would create orphaned records. This can be solved much more easily:

has_many :entities, dependent: :restrict_with_error

于 2019-11-25T14:38:50.570 回答
6

ActiveRecord 关联 has_many 和 has_one 允许一个依赖选项,以确保在删除时删除相关的表行,但这通常是为了保持数据库清洁而不是防止它无效。

于 2008-09-23T22:16:49.957 回答
5

您可以将销毁操作包装在控制器中的“if”语句中:

def destroy # in controller context
  if (model.valid_destroy?)
    model.destroy # if in model context, use `super`
  end
end

在哪里valid_destroy?是模型类上的一个方法,如果满足销毁记录的条件,则返回 true。

拥有这样的方法还可以让您阻止向用户显示删除选项 - 这将改善用户体验,因为用户将无法执行非法操作。

于 2008-09-24T05:11:09.743 回答
4

我最终使用此处的代码在 activerecord 上创建了一个 can_destroy 覆盖: https ://gist.github.com/andhapp/1761098

class ActiveRecord::Base
  def can_destroy?
    self.class.reflect_on_all_associations.all? do |assoc|
      assoc.options[:dependent] != :restrict || (assoc.macro == :has_one && self.send(assoc.name).nil?) || (assoc.macro == :has_many && self.send(assoc.name).empty?)
    end
  end
end

这有一个额外的好处,那就是在 ui 上隐藏/显示删除按钮变得微不足道

于 2013-06-17T19:14:16.860 回答
2

您还可以使用 before_destroy 回调来引发异常。

于 2008-09-23T19:26:17.043 回答
2

我有这些课程或模型

class Enterprise < AR::Base
   has_many :products
   before_destroy :enterprise_with_products?

   private

   def empresas_with_portafolios?
      self.portafolios.empty?  
   end
end

class Product < AR::Base
   belongs_to :enterprises
end

现在,当您删除企业时,此过程会验证是否有与企业关联的产品 注意:您必须将其写在类的顶部才能首先验证它。

于 2014-01-10T23:00:20.893 回答
1

在 Rails 5 中使用 ActiveRecord 上下文验证。

class ApplicationRecord < ActiveRecord::Base
  before_destroy do
    throw :abort if invalid?(:destroy)
  end
end
class Ticket < ApplicationRecord
  validate :validate_expires_on, on: :destroy

  def validate_expires_on
    errors.add :expires_on if expires_on > Time.now
  end
end
于 2017-12-25T12:52:15.037 回答
1

我希望这会得到支持,所以我打开了一个 Rails 问题来添加它:

https://github.com/rails/rails/issues/32376

于 2018-03-29T16:23:56.220 回答