zealoushacker
zealoushacker

Reputation: 6906

Why does destroy_all and delete_all not work consistently for an ActiveRecord::Relation and ActiveRecord::Associations::CollectionProxy?

Why is it that when attempting to call delete_all or destroy_all on a an instance of ActiveRecord::Associations::CollectionProxy, and passing it conditions, the ArgumentError: Valid values are :nullify or :delete_all error is thrown, whereas ActiveRecord::Relation#delete_all does not?

This comes up when attempting to, for example delete_all, given a relationship, such as may exist between Movies and Actors. In this case, given a Movie instance, one may want to do something like:

movie.actors.delete_all(id: [2,3,4])

The above does not work, throwing an error.

The same is true for their counterpart #destroy_all.

Upvotes: 2

Views: 2547

Answers (2)

Ash Pettit
Ash Pettit

Reputation: 457

Despite the old question I thought I'd drop an answer.

ActiveRecord::Relation and ActiveRecord::Associations work differently and can be annoying. They are highly useful though when using destroy to make sure you don't have a DB full of orphans.

That said back to what you were trying to do.

To delete an array of data using active record:

YourApplication::Models::YourClass.destroy_all(selector: value)

DO NOT do this: (This code is incorrect)

YourApplication::Models::YourClass.find(selector: value).destroy_all 

To delete an array that has already been returned

data_to_delete = YourApplication::Models::YourClass.find(selector: value).destroy_all
data_to_delete.each(&:destroy)

Back to the specific question

This code is incorrect

movie.actors.delete_all(id: [2,3,4])

The problem here is your using an AR association to return an array of data and then calling .delete_all on that data (Then hoping that the args of delete_all will act as a SQL type WHERE IN. This is sadly not how AR works.)

The correct way to do this

actors.delete_all(args) 

Where args will be something like movie: 'titanic', id: [1,2,3]

I hope that helps anyone reading this in the future. Be very careful when using delete with associations as Active Record will auto kill orphan data. Delete is the safer option if your unsure.

Upvotes: 5

zealoushacker
zealoushacker

Reputation: 6906

Though it seems like they ought to behave identically, and follow the principle of least surprise, actually behave very surprisingly and differently:

ActiveRecord::CollectionProxy#delete_all and its counterpart #destroy_all take as its argument a dependent parameter, whose default value is nil. This is very different from how ActiveRecord::Relation#delete_all works, which takes a set of conditions, such as those that may be passed to a subclass of ActiveRecord::Base.

So, given the Actor model from above, one may do:

Actor.delete_all(id: [2,3,4])

But, one may not grab a Movie's associated Actor records via the associatino proxy and then invoke delete_all on that.

Upvotes: 2

Related Questions