Rails -- how to structure belongs_to/has_many associations? - ruby-on-rails

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

Related

Delete/Destroy record on join_table with has_many through

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).

When parental record is deleted, are childrecord deleted automatically by accepts_nested_attributes_for?

Here is an example on Rails 3.2 api for accepts_nested_attributes_for:
class Book < ActiveRecord::Base
has_one :author
has_many :pages
accepts_nested_attributes_for :author, :pages
end
Our question is that if a book record is deleted, are child records of author and pages deleted automatically along with the book record? Or we have to explicitly delete the child records in controller.
No, you need to set the dependent key.
has_many :pages, dependent: :destroy
As the OP points out there is another option for delete_all. The difference is that delete_all won't fire the model's before_destroy callbacks, it will just wipe them from the database.
This is beneficial because it doesn't require the Rails to load DB objects into Ruby, which is slow, but it also deletes them regardless of your defined callbacks.

Rails: dependent: :destroy - How does this work?

In an example where there is an association of Owner that is as fellows:
class Owner < ActiveRecord::Base
has_many :buildings, dependent: :destroy
end
The other side of relationship:
class Building < ActiveRecord::Base
belongs_to :owner
end
If I were to delete an Owner, would it destroy the associated Building(s) as well? How can I specify a dependent relationship so that the owner and primary key is no longer associated with any Building(s) if I delete an Owner?
You probably want :nullify. See the Rails docs for has_many.
:dependent 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.
Yes, it will delete the associated buildings as dependent :destroy specified.
If you want to keep the building record, I suggest you use a join table, so that when owner is deleted, only the record in the join table is deleted.

Proper way to delete has_many :through join records?

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.

has_many through association dependent destroy under condition of who called destroy

Is there a way to check, within a before_destroy hook, what object (class) called destroy?
In the following example, when a patient is destroyed, so are their appointments (which is what I want); however I don't want to allow a physician to be destroyed if there are any appointments associated with that physician.
Again, is there a way to do such a check in the before_destory callback? If not, is there any other way to accomplish this "destruction check" based on the "direction" of the call (i.e. based on who called)?
class Physician < ActiveRecord::Base
has_many :appointments, dependent: :destroy
has_many :patients, through: :appointments
end
class Patient < ActiveRecord::Base
has_many :appointments, dependent: :destroy
has_many :physicians, through: :appointments
end
class Appointment < ActiveRecord::Base
belongs_to :patient
belongs_to :physician
before_destroy :ensure_not_referenced_by_anything_important
private
def ensure_not_referenced_by_anything_important
unless patients.empty?
errors.add(:base, 'This physician cannot be deleted because appointments exist.')
false
end
end
end
Note that dependent: :destroy on a has_many :through relationship only deletes the association and not the associated record (i.e. the join records will be deleted, but the associated records won't). So if you delete a patient it will only delete the appointment and not the physician. Read the detailed explanation in the API docs.
I have pasted the relevant paragraphs below.
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.
Just say:
class Physician < ActiveRecord::Base
has_many :appointments, dependent: :restrict_with_exception
has_many :patients, through: :appointments
end
Note the dependent: :restrict_with_exception. This will cause Active Record to refuse to destroy any Physician records that have associated Appointment records.
See the API docs and the association basics guide.

Resources