Michael Lynch
Michael Lynch

Reputation: 1743

has_many of itself through something

So, I have a system where users are able to follow authors (other users).

User Model:

  class User < ActiveRecord::Base
    has_many :author_following, class_name: 'Following'
    has_many :following, through: :author_following, source: :author
    has_many :followers, foreign_key: 'author_id', through: :author_following, source: :user
  end

Following Model:

  class Following < ActiveRecord::Base
    belongs_to :user
    belongs_to :author, foreign_key: 'author_id', class_name: "User"
  end

Issue: I am able to get the list of authors that i am following, but I am able to get the list of my followers.


Given: u is a valid user that is following others and has followers

u.following generates the following SQL:

    SELECT "users".* FROM "users" INNER JOIN "followings" ON "users"."id" = "followings"."author_id" WHERE "followings"."user_id" = $1  [["user_id", 1]]

Which is correct..

u.followers generates the following SQL:

    SELECT "users".* FROM "users" INNER JOIN "followings" ON "users"."id" = "followings"."user_id" WHERE "followings"."user_id" = $1  [["user_id", 1]]

Which is wrong..

Ideally this SQL would be WHERE "followings"."author_id" = $1

Upvotes: 2

Views: 848

Answers (3)

DeeY
DeeY

Reputation: 962

@Billy Chan's answer above is close, but you also need to specify the other side of relationship as well with "association_foreign_key", and switch follower_id with followee_id on our side. Also, join table is users_users actually.

class User < ActiveRecord::Base
  has_and_belongs_to_many :followers, class_name: 'User', 
     foreign_key: 'followee_id', association_foreign_key: 'follower_id'
  has_and_belongs_to_many :followees, class_name: 'User', 
     foreign_key: 'follower_id', association_foreign_key: 'followee_id'
end

  # Migration
    create_table :users_users do |t|
       t.belongs_to :followee
       t.belongs_to :follower
    end

Now User.followers and User.followees work as expected

Upvotes: 0

Billy Chan
Billy Chan

Reputation: 24815

Another way is to use has_and_belongs_to_many. No second model needed.

class User < ActiveRecord::Base
  has_and_belongs_to_many :followers, class_name: 'User', foreign_key: 'follower_id'
  has_and_belongs_to_many :followees, class_name: 'User', foreign_key: 'followee_id'
end

# Migration
create_table :followees_followers do |t|
  t.belongs_to :followee
  t.belongs_to :follower
end

This is simpler, but the validation part(say verifying somebody is an author) need to be done in User model

Upvotes: 1

Michael Lynch
Michael Lynch

Reputation: 1743

Of course, I figure it your right after posting the question. However if you think there is a more elegant way of doing this, please comment :)

To solve, I changed:

User Model:

  class User < ActiveRecord::Base
    has_many :author_following, class_name: 'Following'
    has_many :following, through: :author_following, source: :author
    has_many :author_followers, foreign_key: 'author_id', class_name: 'Following'
    has_many :followers, through: :author_followers, source: :user
  end

Following Model:

  class Following < ActiveRecord::Base
    belongs_to :user
    belongs_to :author, class_name: "User"
  end

Upvotes: 4

Related Questions