user1011792
user1011792

Reputation:

How to prevent Rails from cascading deletion on some relationship

I have the following classes

class Market < ActiveRecord::Base
 has_many :products
 has_many :categories, through: :products
end

class Product < ActiveRecord::Base
 belongs_to :market
 belongs_to : category
end

class Category < ActiveRecord::Base
 has_many :products
 has_many :markets, through: :products
end

Now with the following code:

market = .. #some market
market.categories.destroy_all

market products are deleted as well! How to prevent this ?

Edit Categories don't get deleted in the first place, but only products! And using market.categories.delete_all only delete products in one query instead of many, but again categories aren't deleted! It seems this is the way rails handle this situation of relationships, to destroy products such that market isn't related to categories anymore, so it has the illusion of deleting them! And hence any destroy callback on category isn't called, as category isn't deleted!

Upvotes: 1

Views: 542

Answers (4)

user1011792
user1011792

Reputation:

Ok I think I know the answer: Category and Market are a many-to-many relation ship, so when you say market.categories.delete_all (or destroy_all) it tries to break the relation between this market and its categories, which means deleting the join records, which are produtcs records in our case.

Upvotes: 0

Andrey Deineko
Andrey Deineko

Reputation: 52357

You have to understand the difference between delete_all and destroy_all.

The first one will simply execute the SQL DELETE.

Docs on delete_all:

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.

From docs on destroy_all:

Note: Instantiation, callback execution, and deletion of each record can be time consuming when you’re removing many records at once. It generates at least one SQL DELETE query per record (or possibly more, to enforce your callbacks). If you want to delete many rows quickly, without concern for their associations or callbacks, use delete_all instead.

So to not execute callbacks you should resort to delete, not destroy. On the contrary, destroy is preferable when you have callbacks.

Upvotes: 2

Dushyant
Dushyant

Reputation: 4960

market.categories.delete_all # delete instead of destroy

Upvotes: 0

Alexander Shlenchack
Alexander Shlenchack

Reputation: 3869

market = .. #some market
market.categories.delete_all

But deleting categories will raise problems for products records. The better way using destroy_all and in the before_destroy callback set category_id = nil for products.

Upvotes: 0

Related Questions