What do polymorphic associations solve? - ruby-on-rails

Let's assume I have an application with a "favorites" feature, where users can add a document, note, or comment to his favorites list.
In my mind..
User has_many Favorites
Favorite belongs_to a User
Document belongs_to a Favorite
Note belongs_to a Favorite
Comment belongs_to a Favorite
What's the problem with this type of association and how would polymorphic associations help?

because then your Favorite instance will not know what it is favouriting :)
it knows that it has_one :note, but also has_one :comment, or? but not both surely.
polymorphic association the opposite way helps because it will express that a Favorite object belongs_to :favorited object that is polymorphic cos it can be any class the name of which will be stored in the :favorited_type string db column, so your favorite object will know that it favors a note or document or comment.
with some code
class Note
has_many :favorites, :as => :favorited
has_many :fans, :through => :favorites, :source => :user
end
class Discussion
has_many :favorites, :as => :favorited
has_many :fans, :through => :favorites, :source => :user
end
class Comment
has_many :favorites, :as => :favorited
has_many :fans, :through => :favorites, :source => :user
end
class Favorite
belongs_to :user
belongs_to :favorited, :polymorphic => true # note direction of polymorphy
end
class User
has_many :favorites
has_many :favorite_notes, :through => :favorites, :source => favorited, :source_type => "Note"
has_many :favorite_comments, :through => :favorites, :source => favorited, :source_type => "Comment"
has_many :favorite_discussions, :through => :favorites, :source => favorited, :source_type => "Discussion"
end
(just set up your db correctly) this design is standard for such usecase of favoriting.

Related

Rails has_many through, issues with foreign key and source

A User has many subscribers and many publishers, both of which are users:
class User < ActiveRecord::Base
has_many :relationships, :foreign_key => "subscriber_id"
has_many :subscribers, :through => :relationships, :source => :subscriber
has_many :inverse_relationships, :class_name => "Relationship", :foreign_key => "publisher_id"
has_many :publishers, :through => :inverse_relationships, :source => :publisher
def subscribe_to(publisher)
self.relationships.create!(publisher_id: publisher.id, subscriber_id: id)
end
end
class Relationship < ActiveRecord::Base
belongs_to :subscriber, :class_name => "User"
belongs_to :publisher, :class_name => "User"
end
A user's publishers is who the user is subscribed to.
However, if I do john.subscribe_to(a_publisher), and then attempt to puts john.publishers, I get back an empty array.
I'm going back and forth randomly changing the foreign key and the source, hoping that it will eventually work, but something is off. What should the sources and foreign keys be here?
Update
Here's what I did to make it work:
class User < ActiveRecord::Base
has_many :relationships, :foreign_key => "publisher_id"
has_many :subscribers, :through => :relationships, :source => :subscriber
has_many :inverse_relationships, :foreign_key => "subscriber_id", :class_name => "Relationship"
has_many :publishers, :through => :inverse_relationships, :source => :publisher
def subscribe_to(publisher)
publisher.relationships.create!(subscriber_id: id)
end
end
First, I switched the foreign keys for both. Second, and this is the part I don't understand, I changed
self.relationships.create!(publisher_id: publisher.id)
to
publisher.relationships.create!(subscriber_id: id)
and it worked. For some reason, it doesn't work the other way around. Can anyone explain why?
Your association seems strange. You should try like this:
has_many :subscribers, :through => :relationships, :class_name => 'User'
And call :
john.subscribers.create(a_publisher)
Nowhere you're saying that subscriber is a User. I think your problem comes from here.
Maybe you can try with: :source => :user

Finding the repliers to a comment from a polymorphic comments model

I have this polymorphic association where a user can have many comments, a school can have many comments, and a comment can have many comments (or in my naming case replies):
class Comment < ActiveRecord::Base
attr_accessible :content
has_many :replies, :as => :commentable, :class_name => "Comment" # replies to comments
belongs_to :commentable, :polymorphic => true
belongs_to :commentor, :class_name => "User", :foreign_key => "user_id"
end
class User < ActiveRecord::Base
has_many :comments, :as => :commentable
has_many :commentors, # all users who commented on a user
:through => :comments,
:source => :commentor
end
class School < ActiveRecord::Base
has_many :comments, :as => :commentable
has_many :commentors, # all users who commented on a school
:through => :comments,
:source => :commentor
end
In the User, I can retrieve all who commented on a user using #user.commentors. Same goes for School, i.e. #school.commentors.
For the comments model, I would like to acheive the same thing for the Comments model where I can find all the commentors (or I guess repliers) to a comment; however, I have no idea what kind of association to create since a has_many :through association will not work like how it worked for the User and School model.
Use this:
has_many :reply_commentors, :through => :replies, :source => :commentor

What is the best way to handle 4 way relation between 2 models?

I have two models: Company and User
This is the situation:
Company can follow another company
User can follow a company
User can follow another user
What is the best way to define the relationships and how will the join model look like?
Also, are there any best practises when addressing such situations?
Update
Sorry, to have not mentioned this earlier. I am aware of the various relationship types available. My question is 'which is the best fit'?
Regarding your question I would suggest you to go through couple of Railscasts videos:
http://railscasts.com/episodes/47-two-many-to-many
http://railscasts.com/episodes/154-polymorphic-association
And this is described very well on RubyonRails website
http://guides.rubyonrails.org/association_basics.html
I would say look these for your case:
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
http://guides.rubyonrails.org/association_basics.html#the-has_and_belongs_to_many-association
I hope this will help you.
Thanks to polymorphic associations, we can put all relations into one table which like this:
create_table :follows do |t|
t.references :followable, :polymorphic => true
t.references :followed_by, :polymorphic => true
end
Then the models are:
class User < ActiveRecord::Base
has_many :following_objects, :class_name => 'Follow', :as => :followed_by
has_many :followed_objects, :class_name => 'Follow', :as => :followable
end
class Company < ActiveRecord::Base
has_many :following_objects, :class_name => 'Follow', :as => :followed_by
has_many :followed_objects, :class_name => 'Follow', :as => :followable
end
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :followed_by, :polymorphic => true
end
Sorry for the ugly names.
A basic idea would be to use two self-referencing assocations:
User -> Friendship <- User
Company -> Partnership <- Company
models/user.rb
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
models/friendship.rb
belongs_to :user
belongs_to :friend, :class_name => "User"
models/company.rb
has_many :partnerships
has_many :partners, :through => :partnerships
has_many :inverse_partnerships, :class_name => "Partnership", :foreign_key => "partner_id"
has_many :inverse_partners, :through => :inverse_partnerships, :source => :company
models/partnership.rb
belongs_to :company
belongs_to :partner, :class_name => "Company"
And one many-to-many assocation:
User -> CompanyUser <- Company
models/user.rb
has_and_belongs_to_many :companies
models/company.rb
has_and_belongs_to_many :users
So for this implementation you will need 5 tables (users, friendships, companies, partnerships and companies_users) if you are using a RDBMS.
You can get a nice example in this screencast:
http://railscasts.com/episodes/163-self-referential-association

Nested Has Many Through Plugin and Named Scopes

I have a User Model(:name, :password, :email), and Event model(:name, :etc) and Interest model (:name) [>all singular<]
Then I created two join tables -> UsersInterests and EventsInterests; each not containing a primary key and only comprised of the user_id/interest_id and event_id/interest_id respectively. [>plural<]
My Models Use the Nested Has Many Through Plugin
user.rb => has_many :users_interests
has_many :interests, :through => :users_interests
has_many :events_interests, :through => :interests
has_many :events, :through => :events_interests
event.rb => has_many :events_interests
has_many :interests, :through => :events_interests
has_many :users_interests, :through => :interests
has_many :users, :through => :users_interests
interest.rb => has_and_belongs_to_many :users
has_and_belongs_to_many :events
events_interests.rb => belongs_to :interests
belongs_to :events
users_interests.rb => belongs_to :users
belongs_to :interests
Whew..ok So I wanted to created a named_scope of that find all the events that share interest with a particular user. Here is some code someone helped me with.
named_scope :shares_interest_with_users, lambda {|user|
{ :joins => :users_interests,
:conditions => {:users_interests => {:user_id => user}}
}}
When i run from the controller =>
#user = User.find(1)
#events = Event.shares_interest_with_user(#user)
I get the error :
uninitialized constant Event::EventsInterest
Can anyone see what i messed up?
You must have named something wrong along the way. At a glance I'd say you have a file or class named incorrectly. Remember model names MUST always be singular, both in file and class names or else Rails won't make the connection. Another source of your problem is that arguments to belongs_to must also be singular. Even if you had got things right, the HABTM relationship in interests with users would have thrown an error when you ran the named scope.
I was able to solve your error with the following models.
user.rb
class User < ActiveRecord::Base
has_many :users_interests
has_many :interests, :through => :users_interests
has_many :events_interests, :through => :interests
has_many :events, :through => :events_interests
end
users_interest.rb
class UsersInterest < ActiveRecord::Base
belongs_to :user
belongs_to :interest
end
interest.rb
class Interest < ActiveRecord::Base
has_many :users,:through => :users_interests
has_many :users_interests
has_many :events_interests
has_many :events, :through => :events_interests
end
**events_interest.rb
class EventsInterest <ActiveRecord::Base
belongs_to :interest
belongs_to :event
end
event.rb
class Event <ActiveRecord::Base
has_many :events_interests
has_many :interests, :through => :events_interests
has_many :users_interests, :through => :interests
has_many :users, :through => :users_interests
named_scope :shares_interest_with_users, lambda {|user|
{ :joins => :users_interests,
:conditions => {:users_interests => {:user_id => user}}
}
}
end

question about rails associations

consider this code
class User < ActiveRecord::Base
has_many :views
has_many :posts, :through => :views, :uniq => true
has_many :favorites
has_many :posts, :through => :favorites, :uniq => true
has_many :votes
has_many :posts, :through => :votes, :uniq => true
end
# controller code
user = User.find(3)
posts = user.posts # ??
that said i have established three relationships between posts and users, through different way. But what about the last line??? how can I tell rails that I want to get the posts through views or favorites.
You can give each association a different name, but point it at the same model using the :class_name option. Like so:
class User < ActiveRecord::Base
has_many :views
has_many :view_posts, :through => :views, :class_name => 'Post', :uniq => true,
has_many :favorites
has_many :favorite_posts, :through => :favorites, :class_name => 'Post', :uniq => true
has_many :votes
has_many :vote_posts, :through => :votes, :class_name => 'Post', :uniq => true
end
# Then...
User.find(3).favorite_posts
You may also find named_scope useful.
You have to give the associations different names. The 2nd and 3rd has_many :posts just overwrite the previous ones. You will need something like has_many :view_posts, has_many :favorite_posts, etc.

Resources