Conflicting associations in has_many_polymorphs - ruby-on-rails

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

Related

Dependent destroy does not destroy dependencies

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

In Rails, how would you merge these two different has_manys into one?

Let's say we have a User.
A user has_many documents through account like so…
class User < ActiveRecord::Base
belongs_to :account
has_many :documents, :through => :account, :order => "created_at DESC"
end
class Account < ActiveRecord::Base
has_one :owner, :class_name => "User", :dependent => :destroy
has_many :documents, :dependent => :destroy
end
class Document < ActiveRecord::Base
belongs_to :account
end
Nice and simple, this is where it gets tricky…
A user can also collaborate on documents, this via the collaborators join table…
class Collaborator < ActiveRecord::Base
belongs_to :user
belongs_to :documnet
end
class Document < ActiveRecord::Base
has_many :collaborators, :dependent => :destroy
has_many :users, :through => :collaborators
accepts_nested_attributes_for :collaborators, :allow_destroy => true
end
The final user bit of this is what i'm not sure about. I want to add another has many documents, and when you call user.documents it blends both documents via their account and the ones they're collaborating on…
class User < ActiveRecord::Base
belongs_to :account
has_many :documents, :through => :account, :order => "created_at DESC"
#documents need to do both has manys…
has_many :collaborators, :dependent => :destroy
has_many :documents, :through => :collaborators
end
Thanks, it's a bit long but I can think of a neat solution. Any help would be much appreciated.
You can create a method that will request on the tables documents, accounts and collaborators to find the documents related to the user:
class User < ActiveRecord::Base
#...
def documents
Document.includes(:account, :collaborators).where('collaborators.user_id = ? OR documents.account_id = ?', self.id, self.account.id)
end
end
I've not tested this request, but I hope you get the idea. Please correct it if it's erroneous.
For the 2 has_many documents, :through..., you can remove them if you don't need them anymore; Otherwise, you have to give them different names (and different from the method above).

Help establishing a polymorphic relationship in the models

Models:
class Thread < ActiveRecord::Base
has_many :threaded, :through => :threaded, :foreign_key => :thread_id
class ThreadFeed < ActiveRecord::Base
belongs_to :threaded, :polymorphic => true
Model Fields
Thread (id)
ThreadFeed (id, thread_id, threaded_id, threaded_type)
Problem is with:
#thread.threaded
Rails is using (threaded_id, threaded_type) as the foreign key and I want thread_id to be the foreign key.
Take a look into this Railscast, Polymorphism
It will give you a better insight into how Polymorphism works.
First issue I notice is that it should be through :threadfeed, not :threaded
class Thread < ActiveRecord::Base
has_many :threadfeeds, :as => :threaded
In the Railscast, he has:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Article < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
class Event < ActiveRecord::Base
has_many :comments, :as => :commentable
end
The first problem is that Thread doesn't know what feed is:
class Thread < ActiveRecord::Base
has_many :feeds, :through => :thread_feeds, :as => :feeded
has_many :thread_feeds
class ThreadFeed < ActiveRecord::Base
belongs_to :feeded, :polymorphic => true
The second problem is the complexity of polymorphic. Here's a great article on it: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through

Combining Polymorphic Associations with Rich Many-to-Many Associations

Right now I have a rich many-to-many association with VideoVote as the independent record.
class VideoVote < ActiveRecord::Base
belongs_to :user
belongs_to :video
end
class User < ActiveRecord::Base
has_many :video_votes
has_many :voted_videos,
:through => :video_votes,
:source => :video
end
class Video < ActiveRecord::Base
has_many :video_votes
has_many :voted_users,
:through => :video_votes,
:source => :user
end
However, I want to trasform this into a polymorphic association where comments can also have many VideoVotes (I realize this is confusing, so I should probably change it to Votes). (also, a video will have many comments.) How should I do this?
You first want to add voteable_id:integer and voteable_type:string to your video_votes table.
Then your models will look like:
class VideoVote < ActiveRecord::Base
belongs_to :voteable, :polymorphic => true
end
class Comment < ActiveRecord::Base
has_many :video_votes, :as => :voteable
#code
end
class Video < ActiveRecord::Base
has_many :video_votes, :as => :voteable
#code
end
Then you can access them just like any other has_many:
#video.video_votes
#comment.video_votes
#etc.

How do I define ActiveRecord relationships between two models that relate to each other in two different ways?

In my app I have the classes User, Video, and Vote. Users and Videos can relate to each other in two different ways: as a one-to-many or as a many-to-many. The former is when a User submits a Video (one user can submit many videos). The latter is when a user votes on a video (users have many videos through votes, and vice versa). Here is my code, which does not work (I think -- I may be doing something wrong in the view). Please help me understand the correct way to structure these associations:
class User < ActiveRecord::Base
has_many :videos, :as => :submissions
has_many :votes #have tried it without this
has_many :videos, :as => :likes, :through => :votes
end
class Vote < ActiveRecord::Base
belongs_to :video
belongs_to :user
end
class Video < ActiveRecord::Base
belongs_to :user
has_many :votes #have tried it without this . . . superfluous?
has_many :users, :as => :voters, :through => :votes
end
I haven't gone and checked, but it goes something like this:
Instead of
has_many :videos, :as => :likes, :through => :votes
Use
has_many :likes, :class_name => "Video", :through => :votes
Same with the bottom:
has_many :users, :as => :voters, :through => :votes
becomes
has_many :voters, :class_name => "User", :through => :votes
:as is used for polymorphic associations. See this chapter in docs for more info.
class User < ActiveRecord::Base
has_many :videos # Submitted videos
has_many :votes
has_many :voted_videos, :through => :votes # User may vote down a vid, so it's not right to call 'likes'
end
class Vote < ActiveRecord::Base
belongs_to :video
belongs_to :user
end
class Video < ActiveRecord::Base
belongs_to :user
has_many :votes
has_many :voters, :through => :votes
end
More details can be found here: http://guides.rubyonrails.org/association_basics.html
Hope it helps =)
Thanks for your help guys, definitely pointed me in the right direction. Here is the working code:
class User < ActiveRecord::Base
has_many :videos, :as => :submissions
has_many :votes
has_many :likes, :source => :video, :through => :votes
end
class Vote < ActiveRecord::Base
belongs_to :video
belongs_to :user
end
class Video < ActiveRecord::Base
belongs_to :user
has_many :votes
has_many :voters, :source => :user, :through => :votes
end
PS I kept it as :likes because in this app they won't be able to downvote, only upvote.

Resources