I've a Rails 4 app that uses Postgresql database. I'm using UUIDs as id for my models.
Everything works as expected, I'm trying to set a dependant destroy has many relation, and the "dependant destroy" is not working.
Is there any incompativility between postgress UUIDs and dependent destroy? I need to set foreign keys?
I expalin a bit of my code:
Navigation through models is working correclty
To define the has_many I'm using
has_many :some_models, dependent: :destroy
My migrations are something like:
def change
create_table :my_model, id: :uuid do |t|
To test, I'm using console. I create a relation, delete the "some_models" and the main model is not deleted.
Thanks
You are thinking of the association backwards. dependent: destroy means: When I destroy a parent record, destroy the children that are associated with that record. Here's a contrived example:
class User
has_many :photos, dependent: :destroy
end
When the user is deleted, you want their photos to also be deleted.
If you really want to delete a parent record when a child is deleted, you can do so from the before_destroy callback like so:
class Photo
before_destroy :delete_parent_user
def delete_parent_user
user.destroy if self.user
end
end
Note that other children may still be pointing to that parent record if this is a has_many relationship so this may not be advisable.
dependent: :destroy only destroys child records. When you destroy my_model record, all some_model records belonging to it will be destroyed.
Related
Is there any efficient way to delete all associated records which where created before adding dependant: :destroy ??
Class User
end
Class Post
belongs_to :user, dependent: :destroy
end
Previous records which were created before adding this dependent: :destroy are still present and have user_id present.
eg.
Post.first.user_id = 1
But User.first which has id of 1 is already destroyed before adding the dependent: : destroy.
How do i find and delete these Post records???
Post.where("NOT EXISTS(select 1 from #{User.table_name} where #{Post.table_name}.user_id=#{User.table_name}.id)").delete_all
This should delete all the posts whose associated user no longer exists in the db.
If you want to trigger the callbacks for each Post before deleting them use destroy_all instead.
See: delete_all vs destroy_all? for the difference.
The after_commit callback is not being triggered when the has_many relationship is updated and a record is destroyed.
I have a relationship
class Expertise
has_many :doctor_expertises
has_many :doctor_profiles, through: :doctor_expertises
class DoctorExpertise
belongs_to :doctor_profile
belongs_to :expertise
after_commit :do_something
def do_something
# not called when record destroyed
end
in my controller I use the following method to update the has_many relationship
def create
doc = DoctorProfile.find(params[:doctor_id])
doc.expertise_ids = params[:expertise_ids].select do |x|
x.to_i > 0
end
doc.save!
render json: doc.expertises
end
I understand that I should be using update and destroy on the relationship. However, why is after_commit not getting called on a record when it is destroyed?
I'm guessing it has something to do with the way I'm setting doc.expertise_ids not triggering the callback. However I'm unable to find any documentation about this method except for briefly here. Is there documentation that confirms or denies this suspicion or is there something else going on?
From the RailsGuides that you linked:
Automatic deletion of join models is direct, no destroy callbacks are triggered.
Although it doesn't state about after_commit it's quite likely that isn't being fired too
I think the answer you are looking for is here:
Howto use callbacks in a has_many through association?
You need to user after_remove within the has_many declaration
Updating joins model associations, Rails add and remove records on the collection. To remove the records, Rails use the delete method and this one will not call any destroy callback.
Solution 1
One way to invoke some callbacks when add or remove association is using Association Callbacks.
class Expertise
has_many :doctor_expertises
has_many :doctor_profiles, through: :doctor_expertises
before_remove: :my_before_remove,
after_remove: my_after_remove
def my_before_remove(doctor_profile)
...
end
def my_after_remove(doctor_profile)
...
end
end
Solution 2
Force Rails call destroy instead delete when is removing records.
To do that, install the gem replace_with_destroy and pass the option replace_with_destroy: true to the has_many association.
class Expertise
has_many :doctor_expertises
has_many :doctor_profiles, through: :doctor_expertises,
replace_with_destroy: true
...
end
class DoctorExpertise
belongs_to :doctor_profile
belongs_to :expertise
after_commit :do_something
def do_something
# this will be called when updating Expertise.doctor_profiles
# because use destroyed instead delete to remove the record
end
With this, you ensure Rails invoke all the destroy callbacks.
Just add dependent: :destroy like so
has_many :doctor_profiles, through: :doctor_expertises, dependent: :destroy
I know it's somewhat misleading to have dependent destroy on has_many through, but it will trigger destroy callback and it will not destroy the base records, just the join records. Reference: https://github.com/collectiveidea/audited/issues/246
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.
I have this code:
class FlexField < ActiveRecord::Base
has_many :flex_field_values, class_name: 'FlexFieldValue'
after_save :delete_flex_values
def delete_flex_values
if self.field_type != 'list'
self.flex_field_values.delete_all
end
end
The goal is to delete all values if the type isn't a list. Now what is happening is that as soon as I set the type to something other than list, none of the children get deleted, but their flex_field_id gets set to null.
How can I really have them deleted?
You can write as :
class FlexField < ActiveRecord::Base
has_many :flex_field_values, class_name: 'FlexFieldValue', dependent: :destroy
after_save :delete_flex_values
def delete_flex_values
if self.field_type != 'list'
self.flex_field_values.clear
end
end
end
A brief idea about collection.clear:
Removes every object from the collection. This destroys the associated objects if they are associated with dependent: :destroy, deletes them directly from the database if dependent: :delete_all, otherwise sets their foreign keys to NULL. If the :through option is true no destroy callbacks are invoked on the join models. Join models are directly deleted.
I have a has_many :through model that works perfectly.
has_many :varietals
has_many :grapes, :through => :varietals, :dependent => :destroy
I would like to call another action instead of :destroy. In fact, I don't want to nullify the item OR destroy it, I want to update the record status field from 1 to 0 instead of destroy the record.
How to call a custom method instead of destroy ? I suppose I can do that in the model itself... Thanks.
Where to put this method ? In the master model or in the model where the record will be destroyed ?
EDIT:
I'm sorry but I think I didn't enough explain my problem. My problem is not only to so something after the master model is destroyed. I want to custom the destroy action in the Varietal model itself even if the master record is not destroyed.
Something like:
class Varietal < ActiveRecord::Base
private
def destroy
self.update_attributes(:status => 0)
end
end
Actually this action is not called...
You can use before_destroy to put your custom logic there. E.g.,
before_destroy :reset_status
def reset_status
...
end
Check here for more details.
You just need add a callback on before_destroy or after_destroy and manipulate your associations. By example
after_destroy :do_on_grapes
def do_on_grapes
grapes.map(&:to_do)
end
has_many :dependent is limited to only a few options. According to the documentation:
:dependent If set to :destroy all the associated objects are destroyed
alongside this object by calling their destroy method. If set to
:delete_all all associated objects are deleted without calling their
destroy method. If set to :nullify all associated objects’ foreign
keys are set to NULL without calling their save callbacks. If set to
:restrict this object raises an ActiveRecord::DeleteRestrictionError
exception and cannot be deleted if it has 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.
It looks like you would need to alter the destroy method to update the status field.
I believe that good approach to solve your problem is to provide a custom destroy method. There are several responses to questions like these, but you should keep in mind that ActiveRecord and Relationships like:
class Image < ActiveRecord::Base
has_many :comments, dependent: :destroy
use callback mechanisms that trigger destroy chaining to your relations, too. Usually you should preserve this mechanism and add it to your custom implementation. E.g.
def destroy
self.update deleted_at: Time.now
run_callbacks :destroy
end
You can read this post, too:
Triggering dependent: :destroy with overridden destroy-method