Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby on Rails has_many through self-referential following/follower relationships

Tags:

There are a number of posts and threads on has_many :through, but I haven't found any that cover specifically what I'm trying to do.

I have a User model and a Friendships model. A user has many users that are following them, as well as many followers. It is the usual Twitter model.

For a given user, I want to setup Active Record relationships that return the actual users that are following the user and that the user is a follower of.

These are the relationships that I have setup:

class User < ActiveRecord::Base    has_many :following, :class_name => 'User', :through => :friendships, :foreign_key => 'user_id'    has_many :followers, :class_name => 'User', :through => :friendships, :foreign_key => 'friend_id'  end  class Friendship < ActiveRecord::Base    belongs_to :user   belongs_to :following, :class_name => 'User', :foreign_key => 'friend_id'   belongs_to :follower, :class_name => 'User', :foreign_key => 'user_id'  end 

The Following relationship works - it generates the below join:

SELECT `users`.* FROM `users` INNER JOIN `friendships` ON `users`.id = `friendships`.friend_id WHERE ((`friendships`.user_id = 1)) 

All is grand.

However, the Follower relationship does not work. I've tried a number of variations, but most seem to return the same set of results as Following.

I need the join to be setup as follows to return the correct result set.

SELECT `users`.* FROM `users` INNER JOIN `friendships` ON `users`.id = `friendships`.user_id WHERE ((`friendships`.friend_id = 1));  

Where am I going wrong?

I can set this up using the finder_sql option on the has_many, but it seems like there should be a better way.

has_many :followers, :class_name => 'User', :finder_sql => 'SELECT `users`.* FROM `users` INNER JOIN `friendships` ON `users`.id = `friendships`.user_id WHERE ((`friendships`.friend_id = #{ id }))' 

Thanks!


I made a bit of progress and finally got the relationship working by breaking the relationships into two parts, as was shown in this response: Self-referential has_many :through with customized :primary key issue

# FOLLOWING has_many :friendships_to, :foreign_key => 'user_id', :class_name => 'Friendship' has_many :following, :through => :friendships_to, :class_name => 'User'  # FOLLOWERS has_many :friendships_from, :foreign_key => 'friend_id', :class_name => 'Friendship' has_many :followers, :through => :friendships_from, :class_name => 'User' 

However, while it was possible to have a one-line version of the relationship for following

has_many :following, :class_name => 'User', :through => :friendships, :foreign_key => 'user_id' 

I still wasn't able to get it to work for followers. Still wondering how this could be done?

like image 889
shedd Avatar asked Jul 20 '11 13:07

shedd


2 Answers

You need to make sure ActiveRecord knows what the source association for the User#friends and likewise the followers and specify the class and foreign_key for the relationships that ActiveRecord can't extrapolate from the association names.

class Following < ActiveRecord::Base    belongs_to :user   belongs_to :followed, :class_name => 'User'  end  class User < ActiveRecord::Base    has_many :followings   has_many :friends, :through => :followings, :source => 'followed'    has_many :followeds, :class_name => 'Following', :foreign_key => 'followed_id'   has_many :followers, :through => :followeds, :source => :user  end 
like image 166
james2m Avatar answered Sep 30 '22 09:09

james2m


I'm kind of a noob in a learning process, but this models seem cleaner to me:

class User < ActiveRecord::Base   has_many :followings   has_many :followers, through: :followings end  class Following < ActiveRecord::Base   belongs_to :user   belongs_to :follower, class_name: 'User' end 
  • Source Treehouse : https://www.youtube.com/watch?v=jSUWu50XK48
like image 40
Benjamin Saravia Velarde Avatar answered Sep 30 '22 09:09

Benjamin Saravia Velarde