我有一个批处理工作程序,通过清理表然后重新填充它来更新数据库。应该防止数据库中的特定行被破坏,因此我有before_destroy :dont_destroy_record
回调以确保我不删除特定记录。
我遇到的问题是,当调用collection.destroy_all
,ActiveRecord:RecordNotDestroyed
时,活动记录会抛出异常。只要:dont_destroy_record
回调为这些特定记录返回false,就会抛出此错误。
问题的一些解决方案是将collection.destroy_all
包装在try-catch中,或者我可以遍历每个对象上调用destroy
的集合。这个问题还有其他可能的或更好的解决方案吗?一个限制因素是我无法更改此批处理工作程序,因为它在很多其他项目中使用并且需要超级通用。
我觉得destroy_all
会抛出异常,因为它正在调用destroy
,这似乎很奇怪。我在关于抛出异常的文档中也没有多少看到。
答案 0 :(得分:1)
这个问题有一些很好的评论,我想我会整理一个完整的答案,其中包括一些评论的要点,以及代码示例和每种方法的上行/下行分析。
这样,您可以单独展开destroy_all
到destroy
每个记录。这将允许您检测何时在每条记录上抛出异常,并且只是忽略该异常并继续:
collection.each do |record|
begin
record.destroy
rescue e => ActiveRecord:RecordNotDestroyed
puts "Record #{record.id} not destroyed"
end
end
缺点:
好处:
如果您可以访问验证函数dont_destroy_record
,则可以预先测试集合,并仅销毁那些通过集合的记录。
safe_to_destroy = collection.select {|record| !record.dont_destroy_record }
safe_to_destroy.destroy_all
缺点:
dont_destroy_record
函数dont_destroy_record
不应该有副作用dont_destroy_record
中记录的safe_to_destroy
调用次数加倍,因此可能会影响性能dont_destroy_record
的结果在给定记录的第一次和第二次调用之间发生变化(用户活动,后台进程等),则可能存在竞争条件好处:
destroy_all
这需要访问返回要销毁的collection
的查询。此查询将包括在验证函数中检查的相同条件。
让我们假设这是 Soulless Corp 的任务,并且他们准备选择性地分离(解雇)在工作中至少有30天工作的高薪员工。他们的每月清除过程。原始查询可能如下所示:
collection = Person.joins(:employees).where(salary_level: :highly_compensated).where("person.hire_date < ?", DateTime.now - 30)
还假设验证检查确保Person对象不是老板或没有分配人员。也许条件看起来像这样:
return false if role == boss && employees.any?
我们可以将其转换为单个查询,想象它是这样的:
collection = Person.joins(:employees).where(salary_level: :highly_compensated).where("person.hire_date < ?", DateTime.now - 60).not(role: :boss).group(employee_id).having("COUNT(employees.id) = 0")
将这种方法与我们的虚构模型一起使用会为我们提供一个过滤后的集合,然后我们就可以安全地使用destroy_all
。除了非自愿参加 Soulless Corp 每月职业搬迁计划的员工外,每个人都很开心。
缺点:
好处: