ActiveRecord delete_all method updating instead of deleting - ruby-on-rails

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.

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.

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.

Related

Default dependent action when calling destroy in ActiveRecord / Rails

When calling destroy on a model in ActiveRecord, I've read it is capable of destroying all associated records, and that functionality appears to be setup by using the dependent option when setting up the association.
What I would like to know is - what happens if you don't set the option?
For example, in the below code, am I correct in saying:
The subscribers would NOT be affected
The user would NOT be affected
The comments WOULD be destroyed? (and in turn any associations they have marked with dependent: destroy would also then follow the same process)
class StackOverflowQuestion < ActiveRecord::Base
belongs_to :user
has_many :subscribers
has_many :comments, dependent: :destroy
end
My end goal is to be able to have a model which will destroy some associated records, but not necessarily all of them, as destroying all the associations would mean that data that is reference by other records would start to get wiped out (such as in this example, I'd not want a user to be deleted if their question was deleted).
The subscribers would NOT be affected
This depends on how you define your schema with the foreign key. There are 2 cases here:
Case 1: You define you schema like this:
create_table :subscribers do |t|
t.integer :stack_overflow_question_id
# other fields
end
add_index :subscribers, :stack_overflow_question_id
add_foreign_key :subscribers, :stack_overflow_question, column: :stack_overflow_question_id
This means you set the foreign key constraint for stack_overflow_question_id, so when you delete a StackOverflowQuestion, if there is any Subscriber which has the foreign key referring to that StackOverflowQuestion, rails will give you an error, this makes sense because you are referring a record to a deleted record!
Case 2: Define like Case 1 but without foreign key constraint
Rails won't give you any error, but you will smell wrong with the data, there are some records referring to the deleted records, this should be avoided
The user would NOT be affected
This makes sense because this is belongs_to relation, user wouldn't be affected.
The comments WOULD be destroyed? (and in turn any associations they
have marked with dependent: destroy would also then follow the same
process)
Yes, this is how rails works
Summary
You may redefine like this:
class StackOverflowQuestion < ActiveRecord::Base
belongs_to :user
has_many :subscribers, dependent: :nullify
has_many :comments, dependent: :destroy
end
Hence, your subscribers 's foreign key will be set to NIL when you destroy StackOverflowQuestion, and there isn't any foreign key which is not nil and invalid!
Your description is correct. But you should be aware that the subscribers records will be orphaned. If they are set up with a had_many relation, as you show, then each subscriber record contains a foreign key that is the id of the StackOverflowQuestion record, which will no longer exist after you destroy it. So it will point to an invalid record.

dependent destroy not working

I'm trying to use dependent: :destroy without success.
Lets put a simple example. I create a simple application with the following:
rails g model parent
rails g model child parent:references
Add following lines to parent.rb
has_many :children, dependent: :destroy
I do the following test in rails console (rails c)
p = Parent.create!
c = Child.create!
c.parent = p
c.save
#check association
Child.first == Child.first.parent.children.first
p.delete
#This should return 0
Child.count == 0
And Child.count returns 1.
What I'm missing?
Thanks
4.2.2.4 :dependent
If you set the :dependent option to:
:destroy, when the object is destroyed, #destroy will be called on its associated objects.
:delete, when the object is destroyed, all its associated objects will be deleted directly from the database without calling their #destroy method.
As per your settings, you have to do p.destroy.
The :dependent option can have different values which specify how the deletion is done. For more information, see the documentation for this option on the different specific association types. When no option is given, the behaviour is to do nothing with the associated records when destroying a record.
For has_many, destroy and destroy_all will always call the destroy method of the record(s) being removed so that callbacks are run. However delete and delete_all will either do the deletion according to the strategy specified by the :dependent option, or if no :dependent option is given, then it will follow the default strategy. The default strategy is :nullify (set the foreign keys to nil), except for has_many :through, where the default strategy is delete_all (delete the join records, without running their callbacks).
Calling the delete method on an ActiveRecord derived object will issue a direct DELETE statement do the database, skipping any ActiveRecord callbacks and configurations such as dependent: destroy.
I believe you want the destroy method.
You could also set up a foreign key in the database and set it to cascade on delete, it might make sense, depending on your needs.

destroy_all not calling destroy on each object for has_many through

I have the associations setup in this manner.
class Program
has_many :program_activities, dependent: :destroy
has_many :recent_activities, through: :program_activities, source: :recent_activity
end
class RecentActivity
has_many :program_activities, dependent: :destroy
end
I wanted to delete the recent_activities associated with the program object.
program.recent_activities.destroy_all
But the above query actually just deletes ( please note that deletes and not destroy ) the program_activities and leaves the recent_activities objects alone.
I found this by inspecting the rails console queries, Is there something wrong with the destroy_all method or Have I actually setup the associations incorrectly.
to me there's nothing wrong, it's normal in a many-to-many relationship.
if a recent_activity was related to one and only one program, you shouldn't have a program_activities relationship model. you just should have RecentActivity with a belongs_to :program and the deletion would work.
in your case, you consider that a recent_activity could have many programs. so the deletion of a program can't delete a recent_activity entity because by definition, it can belong to several programs. it will just delete the relationship.
I think there is nothing wrong. It's common that you can just delete the association table when you destroy a model row.
Assume this situation:
Group has_many group_members
Member has_many group_members
Group has_many members through group_members
Member has_many groups through group_members
Is that meaning when we destroy a group, and the relative members should be destroyed too? They are independent model rows, even there is not any groups. And maybe they are of other groups. If they are cascading destroyed, data table will be messy.
Hope this example can explain the logic.
Came across this recently, and this is what ended up working for me:
program.program_activities.each { |activity| activity.recent_activity.destroy }
Not exactly concise, but since the destroy_all method doesn't do what you would expect, this may be the best there is. It does indeed trigger destroy, so in your case, it will take the association with it. If you don't have dependent: :destroy set, you'll get a foreign key violation.

What are the default values for Rails 3 for :dependent on has_many and belongs_to

In rails 3, i know that i can force deletion of dependent objects on belongs_to and has_many relations using the :dependent => :delete option. However i was wondering,
what is the default behavior if i do not specify :dependent => ...
Cheers,
Hajo
The documentation says, "When no option is given, the behavior is to do nothing with the associated records when destroying a record." That is, deleting or destroying an object will not delete or destroy the objects that it belongs to or has many of.
has_many uses the :nullify strategy, which will set the foreign to null. For has_many :through it will use delete_all.
For has_many, destroy will always call the destroy method of the
record(s) being removed so that callbacks are run. However delete will
either do the deletion according to the strategy specified by the
:dependent option, or if no :dependent option is given, then it will
follow the default strategy. The default strategy is :nullify (set the
foreign keys to nil), except for has_many :through, where the default
strategy is delete_all (delete the join records, without running their
callbacks).
-- ActiveRecord::Associations::ClassMethods
Not sure exactly what belongs_to does, and wasn't able to find anything in the docs. I'll try to do some digging soon and update the answer.
In Rails 3, the default :dependent value is :nullify which sets foreign keys to nil.
The default strategy is :nullify for regular has_many. Also, this only works at all if the source reflection is a belongs_to.
Source: http://guides.rubyonrails.org/3_1_release_notes.html#active-record
This is still the case in Rails 4.
However delete and delete_all will either do the deletion according to the strategy specified by the :dependent option, or if no :dependent option is given, then it will follow the default strategy. The default strategy is :nullify (set the foreign keys to nil), except for has_many :through, where the default strategy is delete_all (delete the join records, without running their callbacks).
Source: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Delete+or+destroy%3F
Also see the source code docs: https://github.com/rails/rails/blob/b5a8fd7bb4a6fa4b67d4eabae4cea2cb1834d8d9/activerecord/lib/active_record/associations/collection_proxy.rb#L369

Rails :dependent => :destroy VS :dependent => :delete_all

In rails guides it's described like this:
Objects will be in addition destroyed if they’re associated with :dependent => :destroy, and deleted if they’re associated with :dependent => :delete_all
Right, cool. But what's the difference between being destroyed and being deleted?
I tried both and it seems to do the same thing.
The difference is with the callback.
The :delete_all is made directly in your application and deletes by SQL :
DELETE * FROM users where compagny_id = XXXX
With the :destroy, there is an instantiation of all of your children. So, if you can't destroy it or if each has their own :dependent, its callbacks can be called.
On a Rails' model association you can specify the :dependent option, which can take one of the following three forms:
:destroy/:destroy_all The associated objects are destroyed alongside this object by calling their destroy method
:delete/:delete_all All associated objects are destroyed immediately without calling their :destroy method
:nullify All associated objects' foreign keys are set to NULL without calling their save callbacks
See destroy deletes its associated elements where delete_all can delete multiple data from self table as DELETE * FROM table where field = 'xyz'
:Dependent possible options:
Controls what happens to the associated objects when their owner is destroyed. Note that these are implemented as callbacks, and Rails executes callbacks in order. Therefore, other similar callbacks may affect the :dependent behavior, and the :dependent behavior may affect other callbacks.
:destroy causes all the associated objects to also be destroyed.
:delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not be 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 any associated records.
:restrict_with_error causes an error to be added to the owner if there are any associated objects.
If using with the :through option, the association on the join model must be a belongs_to, and the records which get deleted are the join records, rather than the associated records.
Actually the main difference is that any callbacks will not be invoked when :delete_all was used. But when used :destroy the callbacks stack (:after_destroy, :after_commit ...) will be fired.
Consequently, if you have touch:ing declarations in models being deleted then it's better to use dependent: :delete_all rather 'dependent: :destroy'.

Resources