ActiveRecord delete_all method updating instead of deleting
Asked Answered
E

3

24

I'm using Rails polymorphic associations, that way some models have many cash_histories children, like this:

has_many :cash_histories, as: :cashable

But when I try to delete all cash histories from a parent @resource, like this:

@resource.cash_histories.delete_all

I get the following query:

UPDATE "cash_histories" SET "cashable_id" = NULL WHERE "cash_histories"."cashable_id" = $1 AND "cash_histories"."cashable_type" = $2  [["cashable_id", 1], ["cashable_type", "ServiceOrder"]]

I can't understand this behavior, setting relationship id to null instead of removing, that will result in dead rows in my table. Why is that happening?

I'm using Rails 4.1.

Epitomize answered 27/5, 2014 at 1:35 Comment(1)
When trying to understand this behaviour, remember that you are sending the delete_all method to an association, not directly to a model.Apeldoorn
G
44

From the Rails API docs for delete_all:

Deletes all the records from the collection. For has_many associations, the deletion is done according to the strategy specified by the :dependent option. Returns an array with the deleted records.

If no :dependent option is given, then it will follow the default strategy. The default strategy is :nullify. This sets the foreign keys to NULL. For, has_many :through, the default strategy is delete_all.

So you you just need to set the :dependent option on your has_many to either :delete_all or :destroy, depending on what behavior you want.

has_many :cash_histories, as: :cashable, dependent: :delete_all

From the Rails API docs for has_many:

Objects will be in addition destroyed if they're associated with dependent: :destroy, and deleted if they're associated with dependent: :delete_all.

Georgena answered 27/5, 2014 at 1:49 Comment(2)
Where do you check Rails API? I looked at: apidock.com/rails/v4.0.2/ActiveRecord/Relation/delete_all before posting here and nothing says there.Epitomize
@Jirico, api.rubyonrails.org -- But I pulled up a different docset... the one for ActiveRecord::Associations::CollectionProxy: api.rubyonrails.org/classes/ActiveRecord/Associations/…. It looks like Api Dock has that too if you look for it.Georgena
B
3

its still weird because everywhere else it always describes what happens when the owner is destroyed.

Controls what happens to associated objects when their owner is destroyed:

:destroy causes the associated objects to also be destroyed.

:delete_all causes the associated objects to be deleted directly from the database (callbacks are not executed).

:nullify causes the foreign keys to be set to NULL (callbacks are not executed).

:restrict_with_exception causes an exception to be raised if there are associated records.

:restrict_with_error causes an error to be added to the owner if there are associated objects.

Blackfoot answered 13/10, 2016 at 10:42 Comment(1)
even the current documentation says "not honored" delete_all(conditions = nil) public Deletes the records matching conditions without instantiating the records first, and hence not calling the destroy method nor invoking callbacks. This is a single SQL DELETE statement that goes straight to the database, much more efficient than destroy_all. Be careful with relations though, in particular :dependent rules defined on associations are not honored. Returns the number of rows affected.Blackfoot
B
0

To add to the current answers, as of Rails 7, when accessing an association, the default underlying class returned by Active Record is an ActiveRecord::Associations::CollectionProxy:

@resource.cash_histories.class
# => CashHistory::ActiveRecord_Associations_CollectionProxy

But when you call #all on the collection proxy, you get an ActiveRecord::Relation:

@resource.cash_histories.all.class
# => CashHistory::ActiveRecord_AssociationRelation

The problem that OP is experiencing happens because these classes have a different #delete_all method signature.

For the collection proxy, the #delete_all method accepts a dependency symbol:

@resource.cash_histories.delete_all(:delete_all)
@resource.cash_histories.delete_all(:destroy)
@resource.cash_histories.delete_all(:destroy_async)
@resource.cash_histories.delete_all(:nullify)
@resource.cash_histories.delete_all

As the OP has discovered, the default is :nullify.

On the other hand, the #delete_all method for relation class implements the :delete_all dependency symbol, as you would expect:

@resource.cash_histories.all.delete_all

Hope that helps somebody else.

Brother answered 9/7 at 22:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.