首页 > 解决方案 > 寻求关于优化 ActiveRecord::destroy_all 的建议

问题描述

导轨 5+

我知道destroy_all实例化每个模型并destroy在其上运行并且delete_all速度更快,但删除不尊重:

假设该列表是全面的,我们是否应该destroy_all通过检查模型的这些属性来节省时间,如果没有回调,只需根据需要处理关系吗?

编辑:我正在寻找一种修改默认destroy_all行为的方法,使其更智能,并且不会盲目地实例化所有对象并链接到依赖关系的调用。如果我们有 A 与依赖 B (1:1) 的关系,并且 A 很大(1 百万),那么需要实例化和销毁很多对象。是的,应用程序/领域特定知识意味着您可以调用delete_all,但如果有人更改模型并添加关系,那将delete_all变得非常危险。如果我们进行优化destroy_all以进行一些思考,我们可以将简单的关系从关系 A 上的单个调用(A 和 B)减少dependent: delete到两个调用(A 和 B) ,其中原始将是 200 万个对象实例化和 DB 命中。delete_alldestroy_alldestroy_all

# Pseudocode
# Let the model in question be `User`

ids = self.pluck(:id)
if model.has_destroy_callbacks  # I imagine there's some fancy introspection stuff I can use 
  original_destroy_all
  return
else
  # Check Restrict type
  model.restrict_relationships.each do |rel|
    other_models = some_cute_query
    raise_exception_or_add_error if other_models.any?
  end

  # Add some check here to make sure we didn't miss any unknown dependency type

  # Normal relationships
  model.non_restrict_relationships.each do |rel|
    dep_type = rel.dependent_type
    if dep_type == :destroy
      rel.where(model_id: ids).destroy_all
    elsif dep_type == :delete
      rel.where(model_id: ids).delete_all
    elsif dep_type == :nullify
      rel.where(model_id: ids).update_all(model_name_id: nil)
    end
  end
end
self.delete_all # i.e. the collection that was gonna get destroyed

我正在寻找的是健全性检查,如果我遗漏了一些明显的东西,为什么这不起作用。我也在寻找有关如何将其放入 ActiveRecord 的建议。另外,您可以专门覆盖destroy_all特定模型上的集合/关系吗?

标签: ruby-on-railsactiverecordruby-on-rails-5rails-activerecord

解决方案


The callback chain is accessible via the _*_callbacks method on an object. Active Model Callbacks support :before, :after and :around as values for the kind property. The kind property defines what part of the chain the callback runs in.

To find all callbacks in the before_save callback chain:

Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }

So in this case its _destroy_callbacks but I would make the method raise an exception and bail instead of calling destroy_all if your goal is a sanity check.

raise SomeKindOfError if model._destroy_callbacks.any? 

That's far more helpful in terms of debugging and usage instead of just burying the problem.

Getting all the associations of model can be done via .reflect_on_all_associations which gives you AssocationReflection objects. From there you can get the options of the association.

But...

This reeks of "clever" code. When you get to the point where using destroy callbacks is a performance problem there are bigger problems then just choosing between delete_all or destroy_all and automatically choosing does not really address the problem at all.


推荐阅读