I have the following models:
class Article < ActiveRecord::Base
has_many :comments, :as => :subject, :dependent => :destroy
has_many :deleted_comments, :as => :subject, :dependent => :destroy
end
class DeletedComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
end
class Comment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end
In my database, I have quite a few DeletedComment objects where the subject is nil. The DeletedComment (and Comment) model stores :article_id, and for the ones where the subject is nil, Article.find(deleted_comment.article_id) raises an ActiveRecord::RecordNotFound error.
Are there any cases where the :dependent => :destroy would destroy the parent record but leave the dependencies untouched?
Is it possible that in some cases when I delete an Article the deleted_comments are destroyed before comments? and when the comments are destroyed, deleted_comments are created and not destroyed (because ActiveRecord has already checked the dependent deleted_comment and tried to destroy any dependencies)?
According to official documentation:
Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order for the associations to work as expected, ensure that you store the base model for the STI models in the type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts and member posts that use the posts table for STI. In this case, there must be a type column in the posts table.
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
I guess you could use examle and do something like:
class Article < ActiveRecord::Base
# for deletion only
has_many :abstract_comments, :as => :subject, :dependent => :destroy
# for 'manual' access/edition
has_many :comments, :as => :subject
has_many :deleted_comments, :as => :subject
end
class AbstractComment < ActiveRecord::Base
belongs_to :subject, :polymorphic => true
def attachable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class DeletedComment < AbstractComment
end
class Comment < AbstractComment
before_destroy :create_deleted_comment
def create_deleted_comment
DeletedComment.create!(....)
end
end
Related
I have a fairly normal class structure, using a polymorphic association:
class Contact < ActiveRecord::Base
has_many :opportunities, :as => :has_opportunities, dependent: :destroy
end
class Company < ActiveRecord::Base
has_many :opportunities, :as => :has_opportunities, dependent: :destroy
end
class Opportunity < ActiveRecord::Base
belongs_to :has_opportunities, polymorphic: true
belongs_to :contact, foreign_key: 'has_opportunities_id', conditions: "opportunities.has_opportunities_type = 'Contact'"
belongs_to :company, foreign_key: 'has_opportunities_id', conditions: "opportunities.has_opportunities_type = 'Company'"
end
In Rails 4 using :conditions has been deprecated, but I can't figure out the "new" syntax required to allow access to the parent object from the child.
Edit: Yes, you can do opportunity.has_opportunities which will return you a Contact or a Company, but it is often "nicer" in code to use opportunity.contact or opportunity.company
Can't you simply set it up as a regular polymorphic association?
class Opportunity < ActiveRecord::Base
belongs_to :has_opportunities, polymorphic: true
end
I have a tags model that I'd like to be polymorphic, but I don't want five records for a tag of "video" for example, I want to create the tag once and be able to use it on a variety of models. I've ready some of the questions here about doing that, but I'm not quite getting how to make it work.
So I've got:
class Tag < ActiveRecord::Base
belongs_to :tagable, :polymorphic => true
end
and
class Post < ActiveRecord::Base
has_many :tags, :through => :tag_assignments
end
and
class TagAssignment < ActiveRecord::Base
has_many :tags, :as => :taggable
end
Seems to me that should work, but... reading all the questions here I know I need a :source => option in there somewhere to tie it all together, but I'm just not following exactly how to do it. Can anyone help?
You have to redo your models as follows:
class Tag < ActiveRecord::Base
has_many :tag_assignments
end
class TagAssignment < ActiveRecord::Base
belongs_to :tagable, :polymorphic => true
belongs_to :tag
end
class Post < ActiveRecord::Base
has_many :tag_assignments, :as => :tagable
has_many :tags, :through => :tag_assignments
end
Now given a post you can get its tags as follows:
post.tags
Note
You should consider using the acts-as-taggable-on gem for your use case.
Basically I want a Topic to have many Posts and Posts to have many Comments. If a Post gets destroyed I want it's Comments to be destroyed. If a Topic is deleted, I want it's Posts and Comments destroyed. Does the code below accomplish this? And is the has_one :topic line necessary?
topic.rb:
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
post.rb:
class Post < ActiveRecord::Base
belongs_to :topic, :dependent => :destroy, :touch => true
has_one :topic
has_many :comments, :dependent => :destroy
end
comment.rb:
class Comment < ActiveRecord::Base
belongs_to :post, :dependent => :destroy, :touch => true
end
Should I be using the Ancestry gem for this? Would that make this even more simple? Thanks for reading my questions. Any assistance would be greatly appreciated.
1) has_one :topic is unnecessary, with the belongs_to you already declare the association.
2) :dependent => :destroy goes on the has_many side for your requirements. If you place them on the belongs_to side you will destroy a Topic once destroying one of his posts, leaving orphan a lot of other posts.
This is the code you're looking for:
topic.rb:
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
post.rb:
class Post < ActiveRecord::Base
belongs_to :topic, :touch => true
has_many :comments, :dependent => :destroy
end
comment.rb:
class Comment < ActiveRecord::Base
belongs_to :post, :touch => true
end
Given the following database model, how and where would you define the deletion relationships between the models? I figured out the basic table association setup but when I want to add dependencies to enable the deletion of nested objects I get lost.
Here is the relationship model I created.
class User < ActiveRecord::Base
has_many :studies
end
class Study < ActiveRecord::Base
has_many :internships
belongs_to :student, :class_name => "User", :foreign_key => "user_id"
belongs_to :subject
belongs_to :university, :class_name => "Facility", :foreign_key => "facility_id"
accepts_nested_attributes_for :subject, :university, :locations
end
class Subject < ActiveRecord::Base
has_many :studies
end
class Internship < ActiveRecord::Base
belongs_to :study
belongs_to :company, :class_name => "Facility", :foreign_key => 'facility_id'
accepts_nested_attributes_for :company, :study
end
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
accepts_nested_attributes_for :locations
end
class Location < ActiveRecord::Base
belongs_to :facility
end
Where would you put :dependent => :destroy and :allow_destroy => true to enable the following scenarios? I do not want to confuse you. Therefore, I leave out my tryings.
Internship scenario: A user wants to delete an internship.
Its associated company (facility) can be deleted if the company is not related to another internship.
If so, the locations of the associated company can be deleted.
The related study will not be affected.
Study scenario: A user wants to delete a study.
Its associated subject can be deleted if no other study refers to this subject.
Its associated university (facility) can be deleted if no other study refers to this university.
Its associated internships can be deleted. The company can only be deleted if no other internship refers to it.
I am totally unsure whether I can add :dependent => :destroy only after has_one and has_many or also after belongs_to.
Edit: To simplify the problem please stick to the following (reduced) example implementation.
class Study < ActiveRecord::Base
belongs_to :subject
accepts_nested_attributes_for :subject, :allow_destroy => true
end
class Subject < ActiveRecord::Base
has_many :studies, :dependent => :destroy
end
In my view I provide the following link.
<%= link_to "Destroy", study, :method => :delete, :confirm => "Are you sure?" %>
The path is based on the named routes given by a restful configuration in routes.rb.
resources :studies
resources :subjects
The study will be deleted when I click the link - the subjects stays untouched. Why?
I think your relations are the wrong way around here...
The accepts_nested_attributes_for should be declared on the model that has_many for the model that it has_many of. Also, in your example, destroying the subject would enforce dependent_destroy on the many studies, not the other way around.
You can add :dependent => :destroy to all three but I'm not sure if that'll give you enough power to do the checks required before determining whether an associated object should be destroyed.
You have a few options.
Add a before_destroy callback on each model that raises an exception or stops the delete from occurring.
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def before_destroy
raise SomethingException if internships.any? || ...
# or
errors.add(...
end
end
or do it silently by overriding destroy
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def destroy
return false if internships.any || ...
super
end
end
Note: this is basically meant for guidance only and may not be the correct way of overriding destroy etc...
I'm using has_many_polymorphs to create a "Favorites" feature on a site where multiple users can post stories and make comments. I want users to be able to "favorite" stories and comments.
class User < ActiveRecord::Base
has_many :stories
has_many :comments
has_many_polymorphs :favorites, :from => [:stories, :comments]
end
class Story < ActiveRecord::Base
belongs_to :user, :counter_cache => true
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user, :counter_cache => true
belongs_to :story, :counter_cache => true
end
class FavoritesUser < ActiveRecord::Base
belongs_to :user
belongs_to :favorite, :polymorphic => true
end
Now say #user writes a story. Now #user.stories.size = 1. Then #user favorites a different story. Now #user.stories... wait a minute. #user has_many :stories and :has_many :stories through :favorites.
The issue arises when I attempt to call #user.stories or #user.comments. I want to call #user.stories for stories they own and #user.favorites.stories for stories they favorite.
So I tried this:
class User < ActiveRecord::Base
has_many :stories
has_many :comments
has_many_polymorphs :favorites, :from => [:favorite_stories, :favorite_comments]
end
and then subclassed Story and Comment like so:
class FavoriteStory < Story
end
class FavoriteComment < Comment
end
That fixed the problem because now I can call #user.stories and #user.favorite_stories.
BUT when I get this error in reference to comments:
ActiveRecord::Associations::PolymorphicError in UsersController#show
Could not find a valid class for :favorite_comments (tried FavoriteComment). If it's namespaced, be sure to specify it as :"module/favorite_comments" instead.
I found discussion of this error in a similar context, but it doesn't answer my question.
What's going on here? How can I do this better?
What about something like this?
class UserFavorite < ActiveRecord::Base
belongs_to :user
belongs_to :favorite, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :favourite_story_items, :class_name => "UserFavourite", :conditions => "type = 'Story'"
has_many :favourite_stories, :through => :favourite_story_items, :as => :favourite
has_many :favourite_comment_items, :class_name => "UserFavourite", :conditions => "type = 'Comment'"
has_many :favourite_comments, :through => :favourite_comment_items, :as => :favourite
end