shedd
shedd

Reputation: 4218

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

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

Answers (2)

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

james2m
james2m

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

Related Questions