5

Rails documentation provides a nice explanation of how to handle a self join where only a has_many-belongs_to relationship is required. In the example, an employee (as a manager) can have many employees (each, as a subordinate).

However, how do you handle a has_many-has_many self join (which I've heard referred to as a bi-directional looped association)?

For example, how do you handle the situation in which an employee can have many subordinates, in its capacity as manager, and also have many managers, in its capacity as subordinate?

Or, in other words, where a user can follow many users and be followed by many users?

jbmilgrom
  • 20,608
  • 5
  • 24
  • 22
  • I am trying to replicate @shteef's [question] (http://stackoverflow.com/questions/2168442/many-to-many-relationship-with-the-same-model-in-rails/25389381#25389381) at the end of his response – jbmilgrom Aug 25 '14 at 20:01

1 Answers1

20

A User can have many:

  • followers in its capacity as followee
  • followees in its capacity as follower.

Here's how the code for user.rb might look:

class User < ActiveRecord::Base
  # follower_follows "names" the Follow join table for accessing through the follower association
  has_many :follower_follows, foreign_key: :followee_id, class_name: "Follow" 
  # source: :follower matches with the belong_to :follower identification in the Follow model 
  has_many :followers, through: :follower_follows, source: :follower

  # followee_follows "names" the Follow join table for accessing through the followee association
  has_many :followee_follows, foreign_key: :follower_id, class_name: "Follow"    
  # source: :followee matches with the belong_to :followee identification in the Follow model   
  has_many :followees, through: :followee_follows, source: :followee
end

Here's how the code for follow.rb:

class Follow < ActiveRecord::Base
  belongs_to :follower, foreign_key: "follower_id", class_name: "User"
  belongs_to :followee, foreign_key: "followee_id", class_name: "User"
end

The most important things to note are probably the terms :follower_follows and :followee_follows in user.rb. To use a run of the mill (non-looped) association as an example, a Team may have many :players through :contracts. This is no different for a Player, who may have many :teams through :contracts as well (over the course of such Player's career).

But in this case, where only one named model exists (i.e. a User), naming the through: relationship identically (e.g. through: :follow) would result in a naming collision for different use cases of (or access points into) the join table. :follower_follows and :followee_follows were created to avoid such a naming collision.

Now, a User can have many :followers through :follower_follows and many :followees through :followee_follows:

  • To determine a User’s :followees (upon an @user.followees call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the follower (i.e. foreign_key: :follower_id) through: such User’s :followee_follows.
  • To determine a User’s :followers (upon an @user.followers call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the followee (i.e. foreign_key: :followee_id) through: such User’s :follower_follows.
jbmilgrom
  • 20,608
  • 5
  • 24
  • 22
  • that's a nice answer, but what if he needs an isotropic connection? – Marian Theisen Aug 25 '14 at 20:53
  • Thanks @MarianTheisen. I'm not sure I understand the question. In this situation, followers and followees are totally independent groups of users. There can be between zero and 100% overlap between the groups. Are you talking about a situation in which 100% overlap is a requirement of the self join table connecting users? – jbmilgrom Aug 26 '14 at 12:59
  • no i mean, your solution implies a directed connection between 2 users, i.e.: user A follows user B, user B could follow user A as well, but doesn't have to. (A -> B, B -> A). But what about a solution if the direction doesn't matter (A <-> B), i.e. person A is a relative of person B? – Marian Theisen Aug 26 '14 at 13:54
  • ahhh, ok. Like you are saying, in this case, you wouldn't have to worry about identifying a "follower" or "followee" as between two users, just that there is a connection between such users. I think it actually turns out to be much simpler. I learned about how to do that from @shteef's answer here: http://stackoverflow.com/questions/2168442/many-to-many-relationship-with-the-same-model-in-rails?lq=1 He talks about both the scenario where you need additional fields ("uni-directional, no additional fields") in the join table and where you do not ("Uni-directional, with additional fields") – jbmilgrom Aug 26 '14 at 14:40
  • 1
    Sorry, I meant to say...He talks about both the scenario where you do not need additional fields ("uni-directional, no additional fields") in the join table and where you do ("Uni-directional, with additional fields"). – jbmilgrom Aug 26 '14 at 14:49
  • just to add one thing, using @shteef's answer as an example, in the Uni-directional (i.e. isotropic) case, you don't have to worry about the naming collision that occurs with the `through:` association in the bi-directional case, which is why he was able to give the `through:` association the same name (i.e. `through: :post_connections`) as the name of the actual join model (i.e. `PostConnection`). – jbmilgrom Aug 26 '14 at 15:00
  • 1
    This can be contrasted with the bi-directional case, in which the `through:` associations need to be named differently (i.e. `through: :followee_follows` and `through: :follower_follows`) than the join model (i.e. `Follow`) to account for the bi-directional nature of the association – jbmilgrom Aug 26 '14 at 15:02
  • 1
    See [my blog post](https://medium.com/@jbmilgrom/active-record-many-to-many-self-join-table-e0992c27c1e) for a more complete discussion – jbmilgrom Feb 27 '15 at 17:33
  • @jbmilgrom Thank you so much for your answer - this helped me get my model up and running. Could you share an example of how to actually create an object that has this type of model? My model appears to work if I populate the relationship table manually, but I'm not sure of the best "Rails-y" way to create these relationships, particularly as nested attributes. – Wemmick Oct 08 '15 at 17:46
  • @Wemmick I'm glad! I actually haven't worked in rails in a long, long time, so I'm probably not the best person to answer your question, but I thiiink I may have covered what you asking here: https://medium.com/@jbmilgrom/active-record-many-to-many-self-join-table-e0992c27c1e Hope that helps! – jbmilgrom Oct 08 '15 at 17:56
  • @jbmilgrom Still stuck, but if you happen to remember any rails, I posted [my question here](http://stackoverflow.com/questions/33028062/how-to-create-update-objects-in-rails-in-a-many-to-one-self-referential-model-us) – Wemmick Oct 09 '15 at 02:10