0

I am trying to perform delete_all on an ActiveRecord relation that has a lot of rows, and one level dependents.

When I used the destroy_all, it was of course painfully slow, since it iterated over the entire relation.

When using delete_all, I was unable to find a way to specify that rails should also delete (not destroy) its dependents.

This is what I know and tried:

  1. I have a Member model and a Message model.
  2. In the member model I have
    has_many :messages, inverse_of: :member, dependent: :destroy
  3. I thought that changing the above to dependent: :delete_all would delete all the dependents using one query (delete messages where member ID in [array of members to be deleted]) - it did not, and instead failed with a foreign key error.
  4. I have already spent time in the appropriate Rails documentation pages (like this one) and in some related StackOverflow questions (like this one).

The solution I found so far is this:

# Define the Member collection we want to delete
members = Member.where comment: 'debug'

# Get all the IDs from it
ids = members.pluck :id

# Delete all the dependent Message objects first
Message.where(member_id: ids).delete_all

# Then delete the Members
members.delete_all

This is fast, and properly results in two delete queries.

My questions are:

  • Wasn't the dependent: :delete_all definition supposed to do this for me?
  • Is there a more Rails-like way to do it? It feels like I am doing manually something that should have been taken care of by rails itself.
DannyB
  • 12,810
  • 5
  • 55
  • 65
  • 2
    You can always write a method in your model and delete ur dependents. You can invoke that method automatically by binding it to delete the event. So whenever you trigger delete on the parent, the method is invoked automatically and deletes children first. Make sure u delete the dependants first before deleting the main parent. – Harry Bomrah Jul 02 '20 at 08:26
  • `before_destroy` callback for destroying.... but still it takes 2 SQL queries... I don't think there is some other resolution. – SulmanWeb Jul 02 '20 at 20:22

1 Answers1

1

I have also done this before where I have overridden my destroy method. At the time, I did my extensive research and did exactly what you have done. If you end up overriding destroy, make sure to wrap your method in a transaction. This will protect you by rolling back your deletes if you get errors in one of your dependents.

Btw, you can chain your where and pluck and save 1 call from the db.

member_ids = Member.where(comment: 'debug').pluck(:id)

kurokyjr
  • 31
  • 2
  • Thanks. I am not overriding `destroy` - seems dangerous to me. Was hoping I missed something, and rails has a built in way to do this efficiently. – DannyB Jul 11 '20 at 07:35