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?
A User can have many:
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
:
@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. @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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With