class Post < ActiveRecord::Base
has_many :posts_tags
has_many :tags, through: :posts_tags
end
class PostsTag < ActiveRecord::Base
belongs_to :post
belongs_to :tag
end
class Tag < ActiveRecord::Base
has_many :posts_tags
has_many :posts, through: :posts_tags
end
When Post gets destroyed I want all of its associations to Tag deleted as well. I do NOT want validations on PostsTag model to run. I just want to deleted.
I've found that adding a dependent on the relationship to posts tags from the Post model works as I want: has_many :posts_tags, dependent: :delete_all.
However, the documentation on the subject seems to suggest that I should do this instead: has_many :tags, through: :posts_tags, dependent: :delete_all. When I do this, the Tag object gets destroyed and the join object remains.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
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).
How can I have the default strategy actually used? If I leave :dependent off completely, no records are removed at all. And I cannot just indicate :dependent on a has_many relationship. Rails comes back and says "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict ({})".
If I don't specify :dependent on either of the relationships, it does NOT nullify the post_id on the PostsTag object as it seems to suggest
Perhaps I am reading this wrong and the approach that I found works is the correct way?
Your original idea of:
has_many :posts_tags, dependent: :delete_all
is exactly what you want. You do not want to declare this on the has-many-though association :tags, as that will destroy all associated Tags. What you want to delete is the association itself - which is what the PostTag join model represents.
So why do the docs say what they do? You are misunderstanding the scenario that the documentation is describing:
Post.find(1).destroy
Post.find(1).tags.delete
The first call (your scenario) will simply destroy the Post. That is, unless you specify a :dependent strategy, as I suggest you do. The second call is what the documentation is describing. Calling .tags.delete will not (by default) actually destroy the tags (since they are joined by has-many-through), but the associated join model that joins these tags.
Related
I render a set of checkboxes and I want to delete all the Privilege of a User.
When I check one of the checkboxes, a record is created.
When I uncheck one of the checkboxes, a record is deleted.
I have searched all the questions related, but unluckily none works.
Rails 5.2.3
User
has_many :user_privileges, class_name: 'UserPrivileges'
has_many :privileges, through: :user_privileges
Privilege
has_many :user_privileges, class_name: 'UserPrivileges'
has_many :users, through: :user_privileges
UserPrivileges
belongs_to :user
belongs_to :privilege
The issue kick in when I want to delete ( uncheck ) the last privilege-record of that user in the join_table.
The record is still there, and there is no way to delete/destroy that specific record.
My intuition recall to the callbacks, I have tried different ways of using dependent but the last record is still there.
Any tips are welcome.
Thanks
If you want to delete the record from the join table, you need to add dependent: :destroy to has_many :through relationship.
# privilege.rb
has_many :user_privileges, class_name: 'UserPrivileges'
has_many :users, through: :user_privileges, dependent: :destroy
See What gets deleted? in API docs:
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.
To run dependent: :destroy callback, you must use the destroy or destroy_all method when deleting the privilege record.
See Delete or destroy? in API docs:
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 to do nothing (leave the foreign keys with the parent ids
set), except for has_many :through, where the default strategy is
delete_all (delete the join records, without running their callbacks).
In my project there are many models with has_many association and dependant: :destroy flag. Also, each model have other belong_to associations with the dependant: :destroy flag. This models are nested between each other so when a destroy is executed on the top one, Rails triggers cascading destroy on children models.
Apart from that, models have callbacks that execute before_destroy.
The following represents what I described above:
class Model1Example < ActiveRecord::Base
has_many :model2_examples, :dependent => :destroy
belongs_to :other1_example, :dependent => :destroy
belongs_to :other2_example, :dependent => :destroy
end
class Model2Example < ActiveRecord::Base
belongs_to :model1_example
has_many :model3_examples, :dependent => :destroy
belongs_to :other3_example, :dependent => :destroy
belongs_to :other4_example, :dependent => :destroy
before_destroy :update_something_on_model1
before_destroy :check_some_inconsistence
end
class Model3Example < ActiveRecord::Base
belongs_to :model2_example
belongs_to :other5_example, :dependent => :destroy
belongs_to :other6_example, :dependent => :destroy
before_destroy :update_something_on_model2
before_destroy :check_some_inconsistence
end
Given that on average Model2Example holds about 100+ instances of Model3Example when the Model1Example destroy is triggered many SQL queries are triggered (10k+) because deletion is record by record and also all rules are executed for every instance...and this takes a lot more than what a user could wait for such a simple action.
I could fix this performance issue by using dependant: :delete_all on the has_many associations instead, because I don't really care that all this rules are executed when I trigger Model1Example destroy.
But the problem is that when I execute (from elsewhere in the app) a Model2Example destroy is in my interest that all rules are executed (specially Model3Example rules for each instance), and the previous mentioned approach brakes this.
Is there a "Rails way" to achieve a performance improvement for this case? Or should I just use SQL for Model1Example deletion?
Also, if I have to use this approach and I wanted to check some basic stuff before destroying Model1Example, where is the best place to do this validation? controller?
I have this model
class XmlImport < ActiveRecord::Base
belongs_to :video
belongs_to :user
has_many :events, through: :event_import_records, dependent: :destroy
has_many :event_import_records, dependent: :destroy
has_attached_file :xml
validates_attachment_content_type :xml, :content_type => ["text/xml"]
end
The :event_import_records entries are being destroyed. But the :events are not.
Is the dependent:destroy on the has_many through association valid?
Is there another way of writing it? If that is not correct
How can I destroy all the events associated to the XmlImport through the event_import_records?
You can find at the Rails API that: "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." I understand that it delete the joins records but not the associated by through.
If I were you, I try:
class EventImportRecord < ActiveRecord::Base
has_many :events, dependent: :destroy
end
If not work I swap the order of the has_many relations on the XmlImport model, because of "Note that :dependent is implemented using Rails' callback system, which works by processing callbacks in order. Therefore, other callbacks declared either before or after the :dependent option can affect what it does." Also find at the same page of the Rails API.
Pretty simple question, I think: so I have a User model, a Product model, and a Comment model. I want the Users to be able to comment on specific products (like leave reviews for the products).
Is this the correct structure?
User
has_many :comments
Product
has_many :comments
Comment
belongs_to :user
belongs_to :product
Thanks.
yes it's correct if you want just comment on products, if you will comment on other model other than product, then use polymorphic association.
also don't forget to add dependent: :destroy to destroy related comments if the product is destroyed or the user is destroyed
in Product and User model add dependent: :destroy
has_many :comments, dependent: :destroy
if you want other behavior than this, there is other options, from Doc :
:dependent
Controls what happens to the associated objects when their owner is
destroyed:
: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 execute)
: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 object
Apparently dependent => destroy is ignored when also using the :through option.
So I have this...
class Comment < ActiveRecord::Base
has_many :comment_users, :dependent => :destroy
has_many :users, :through => :comment_users
...
end
...but deleting a Comment does not result in the associated comment_user records getting deleted.
What's the recommended approach, then, for cascade deletes when using :through?
Thanks
Apparently :dependent is not ignored!
The real issue was that I was calling Comment.delete(id) which goes straight to the db, whereas I now use Comment.destroy(id) which loads the Comment object and calls destroy() on it. This picks up the :dependent => :destroy and all is well.
The original poster's solution is valid, however I wanted to point out that this only works if you have an id column for that table. I prefer my many-to-many tables to only be the two foreign keys, but I had to remove my "id: false" from the migration table definition for cascading delete to work. Having this functionality definitely outweighs not having an id column on the table.
If you have a polymorphic association, you should do what #blogofsongs said but with a foreign_key attribute like so:
class User < ActiveRecord::Base
has_many :activities , dependent: :destroy, foreign_key: :trackable_id
end