Nithin Bekal

Posts About Notes Slides

Complex has_many :through associations in Rails

16 Apr 2014

has_many :through associations can get a bit complicated when they involve self-referential relationships. Let’s take Twitter as an example to state this problem. User A follows users B and C, but not D. This is represented by the Follow model. Each User also has may tweets.

We now need to define an association followed_tweets on users such that a.followed_tweets will return all the tweets by users B and C, but not the ones by D.

class User
  has_many :tweets

  has_many :follows
  has_many :followed_users, through: :follows
  has_many :followers, through: :follows, inverse_of: :follower
end

class Follow
  belongs_to :follower, class_name: 'User', foreign_key: 'follower_id'
  belongs_to :followed_user, class_name: 'User', foreign_key: 'followed_user_id'
end

class Tweet
  belongs_to :user
end

In migrations:

create_table :users do |t|
  # ...
end

create_table :tweets do |t|
  t.references :user
  # ...
end

create_table :follows do |t|
  t.references :follower
  t.references :followed_user
end

Before we can define the followed_tweets relation on User, we must first define a followed_tweets relation on Follow.

class Follow
  has_many :followed_tweets, through: :followed_user, source: :tweets
  # ...
end

Now we can use follow.followed_tweets instead of follow.followed_user.tweets. This also means that we can write a has_many :followed_tweets, through: :follows relation on the User model.

Here’s what the relations look like now:

class User
  has_many :tweets

  has_many :follows
  has_many :followers, through: :follows, inverse_of: :follower
  has_many :followed_users, through: :follows

  has_many :followed_tweets, through: :follows
end

class Follow
  belongs_to :follower, class_name: 'User', foreign_key: 'follower_id'
  belongs_to :followed_user, class_name: 'User', foreign_key: 'followed_user_id'
  has_many :followed_tweets, through: :followed_user, source: :tweets
end

class Tweet
  belongs_to :user
end

Here’s a blog post that clearly explains self-referential has_many :through associations. (Warning: the examples are from Rails 2.0.) In our example, that would be the association representing a user having many followers or followed_users.

However, we have explored this to one more level, ie. a has_many :through association where the “through” attribute indirectly references the same model (User) as the source.

Hi, I’m Nithin Bekal, a software craftsman with over 7 years of experience in shipping web applications. I mostly use Ruby, but lately have also been exploring Elixir. Co-founder of CrowdStudio.in, and helping organize Rubyconf India. Tweet to me at @nithinbekal.