如何在导轨上销毁上“验证”


Answers:


70

您可以提出一个异常,然后捕获。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在返回时填充。


3
请注意,现在它说“ ...好的,继续销毁”时,您需要放置“ super”,因此实际上会调用原始的destroy方法。
2009年

3
在Rails 3中不建议使用errors.add_to_base。相反,应该执行errors.add(:base,“ message”)。
瑞安

9
Rails在销毁之前不会进行验证,因此before_destroy将需要返回false来取消销毁。仅添加错误是没有用的。
graywh 2012年

24
使用Rails 5时,的false结尾before_destroy是没有用的。从现在开始,您应该使用throw(:abort)(@see:weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/…)。
romainsalles

1
您可以通过以下方法轻松解决保护孤儿记录的示例has_many :booking_payments, dependent: :restrict_with_error
thisismydesign '19

48

只是一个注释:

护栏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

2
这种方法的问题在于,在销毁所有booking_payments之后,似乎调用了before_destroy回调。
sunkencity 2012年

4
相关票证:github.com/rails/rails/issues/3458 @sunkencity,您可以在关联声明之前声明before_destroy,以暂时避免这种情况。
lulalala 2013年

1
您可以通过以下方法轻松解决保护孤儿记录的示例has_many :booking_payments, dependent: :restrict_with_error
thisismydesign '19

根据rails指南,before_destroy回调可以并且应该放在与dependent_destroy关联之前。这会触发回调关联破阵被称为前:guides.rubyonrails.org/...
grouchomc

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

3
这是一个很好的文章,解释对Rails 5此行为:blog.bigbinary.com/2016/02/13/...
薯芋Holodiuk

1
您可以通过以下方法轻松解决保护孤儿记录的示例has_many :qrcodes, dependent: :restrict_with_error
thisismydesign '19

6

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


1
下划线(如果它们是函数名称或类似名称的一部分)的另一种保护方法是将其包装在反引号中。然后将显示为代码like_so
理查德·琼斯

谢谢。您的回答使我又对有关从属期权类型的搜索在这里得到了解决:stackoverflow.com/a/25962390/3681793
bonafernando

还有一些dependent选项不允许删除实体(如果它会创建孤立的记录)(这与问题更相关)。例如dependent: :restrict_with_error
-thisismydesign

5

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

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

哪里有效?是模型类上的方法,如果满足销毁记录的条件,则该方法返回true。

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


7
无限循环,有人吗?
jenjenut233 2011年

1
很好,但是我假设该方法在控制器中,因此要遵循模型。如果在模型中,则肯定会引起问题
Toby Hede

呵呵,抱歉,我知道你的意思,我只是看到“模型类中的方法”并很快想到“呃哦”,但是你是对的-在控制器上销毁,可以正常工作。:)
jenjenut233 2011年

一切都很好,实际上最好是要非常清楚,而不是使清晰度不佳的贫困初学者的生活变得困难
Toby Hede

1
我也考虑过在Controller中执行此操作,但是它确实属于Model,因此无法从控制台或可能需要销毁这些对象的任何其他Controller中销毁对象。保持干燥。:)
约书亚·品特

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上隐藏/显示删除按钮变得微不足道的额外好处。


3

从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

由于throw(:abort)需要取消Rails 5的执行,因此:https : //makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chain

prepend: true是必需的,以便dependent: :destroy在执行验证之前不会运行:https : //github.com/rails/rails/issues/3458

您可以从其他答案和评论中找到答案,但是我发现它们都不完整。

附带说明一下,许多人以has_many关系为例,在这种情况下,如果要创建孤立记录,他们要确保不要删除任何记录。这可以很容易地解决:

has_many :entities, dependent: :restrict_with_error



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

现在,当您删除企业时,此过程将验证是否有与企业相关联的产品。注意:必须首先在类的顶部编写此代码,以便首先对其进行验证。


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

您无法验证on: :destroy,请参见此问题
thesecretmaster

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.