Rails 5+
I'm aware that destroy_all
instantiates every model and runs destroy
on it and that delete_all
is faster, but deleting doesn't respect:
before_destroy
, around_destroy
and after_destroy
callbacks dependent
settings on relationships Assuming that list is comprehensive, shouldn't we be able to save time with destroy_all
by checking these properties of the model and if there are no callbacks, just address the relationships as needed?
edit: I'm looking for a way to modify the default destroy_all
behaviour so that it's smarter and doesnt blindly instantiate all objects and chain calls to dependent relationships. If we have relationship A with dependent B (1:1), and A is large (1 mil) that's a lot of objects to instantiate and destroy. Yes, application/domain specific knowledge means you can just call delete_all
but if someone changes the model and adds relationships, that delete_all
just became very dangerous. If we optimize destroy_all
to do some thinking, we can reduce a simple dependent: delete
relationship to two delete_all
calls (A and B) from a single destroy_all
on relation A, where the original destroy_all
would be 2 million object instantiations and DB hits.
# 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
What I'm looking for is sanity checks on if I'm missing something obvious as to why this won't work. I'm also looking for suggestions on how I can shimmy this into ActiveRecord. Also, can you specifically override the destroy_all
for collections/relationships on specific models?
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.
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.