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.
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
I have a role attribute on the ProductUser model. Users can be deleted by the owner (role), and I wanna make sure the owner can't delete himself while the product exists. However, when the product gets deleted all the product_users should be gone including the owner.
The following code throws an ActiveRecord::RecordNotDestroyed - Failed to destroy the record error when I try to delete the product. I guess this is because of the order of the execution due to the dependent: :destroy. How can I make this work?
class ProductUser < ActiveRecord::Base
belongs_to :user
belongs_to :product, touch: true
before_destroy :check_for_owner
def check_for_owner
if product && role == "owner" #If I use simply: if role == "owner", it still doesn't work.
errors[:base] << "Owner can't be deleted!"
return false
end
end
end
class Product < ActiveRecord::Base
has_many :product_users, dependent: :destroy
....
end
Have you considered simply not showing the delete button for the ProductUser record if the user is the owner?
In any case, if you use a foreign key rather than dependent destroy then the destroying will happen at the database level and the product user models won't be instantiated which would solve your issue.
So in a migration
def change
add_foreign_key :product_users, :products, on_delete: :cascade
end
Then on product remove the dependent destroy option
class Product < ActiveRecord::Base
has_many :product_users
end
With the on delete cascade option the database will destroy the product's product users when the product is deleted.
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.
I have three activerecord classes: Klass, Reservation and Certificate
A Klass can have many reservations, and each reservation may have one Certificate
The definitions are as follows...
class Klass < ActiveRecord::Base
has_many :reservations, dependent: :destroy, :autosave => true
has_many :certificates, through: :reservations
attr_accessible :name
def kill_certs
begin
p "In Kill_certs"
self.certificates.destroy_all
p "After Destroy"
rescue Exception => e
p "In RESCUE!"
p e.message
end
end
end
class Reservation < ActiveRecord::Base
belongs_to :klass
has_one :certificate, dependent: :destroy, autosave: true
attr_accessible :klass_id, :name
end
class Certificate < ActiveRecord::Base
belongs_to :reservation
attr_accessible :name
end
I would like to be able to delete/destroy all the certificates for a particular klass within the klass controller with a call to Klass#kill_certs (above)
However, I get an exception with the message:
"In RESCUE!"
"Cannot modify association 'Klass#certificates' because the source
reflection class 'Certificate' is associated to 'Reservation' via :has_one."
I('ve also tried changing the reservation class to "has_many :certificates", and then the error is...
"In RESCUE!"
"Cannot modify association 'Klass#certificates' because the source reflection
class 'Certificate' is associated to 'Reservation' via :has_many."
It's strange that I can do Klass.first.certificates from the console and the certs from the first class are retrieved, but I can't do Klass.first.certificates.delete_all with out creating an error. Am I missing something?
Is the only way to do this..
Klass.first.reservations.each do |res|
res.certificate.destroy
end
Thanks for any help.
RoR docs have clear explanation for this (read bold only for TLDR):
Deleting from associations
What gets deleted?
There is a potential pitfall here: has_and_belongs_to_many and
has_many :through associations have records in join tables, as well as
the associated records. So when we call one of these deletion methods,
what exactly should be deleted?
The answer is that it is assumed that deletion on an association is
about removing the link between the owner and the associated
object(s), rather than necessarily the associated objects themselves.
So with has_and_belongs_to_many and has_many :through, the join
records will be deleted, but the associated records won’t.
This makes sense if you think about it: if you were to call
post.tags.delete(Tag.find_by(name: 'food')) you would want the ‘food’
tag to be unlinked from the post, rather than for the tag itself to be
removed from the database.
However, there are examples where this strategy doesn’t make sense.
For example, suppose a person has many projects, and each project has
many tasks. If we deleted one of a person’s tasks, we would probably
not want the project to be deleted. In this scenario, the delete
method won’t actually work: it can only be used if the association on
the join model is a belongs_to. In other situations you are expected
to perform operations directly on either the associated records or the
:through association.
With a regular has_many there is no distinction between the
“associated records” and the “link”, so there is only one choice for
what gets deleted.
With has_and_belongs_to_many and has_many :through, if you want to
delete the associated records themselves, you can always do something
along the lines of person.tasks.each(&:destroy).
So you can do this:
self.certificates.each(&:destroy)