Reputation: 4218
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?
Upvotes: 24
Views: 9058
Reputation: 59
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
Upvotes: 5
Reputation: 1580
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
Upvotes: 24