Many-to-many self join in rails? - ruby-on-rails

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:
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.

Related

Confused about data model in Hartl's railstutorial in Chapter 12

So in chapter 12 or Hartl's Railstutorial, we're building the ability for users to follow one another's "twitter" feeds. This is modeled as users forming relationships and we create a relationship model with a table that has a follower_id and a followed_id. Also in the model, we associate it with the user model as follows:
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
end
We also associate the user model with the relationship model as follows:
class User < ActiveRecord::Base
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
I'm confused as to why we need to have has_many :following in the user model. It says in the tutorial that following someone is an active relationship, so why do we need to say users have many active relationships, and users are also following many (which is an active relationship). What exactly is has_many :following doing that has_many :active_relationships cannot do?
Also my second question is why the belongs_to is split into follower and followed, instead of just user. What do we gain by using two belongs_to instead of just one on the user?
It is a way to access the Users that are either following or being followed by a particular user instead of the relationships.
If you just had #user.active_relationships that would return back the relationships in the join table. But with #user.following you get an association array of User objects.
And as for your second question, a relationship between two users takes 2 objects not one, and would be pointless to have just a single belongs_to :user.
Ruby on Rails Guides - Associations | Has many :through

How to implement ActiveRecord has_many through polymorphic association with different source types

I have 3 Models: Vote, Student and Teacher. A Student can give a vote for either another student or a teacher. Previously a student could only give a vote for one student or one teacher. But now i want to have the option to vote for multiple students and teachers. The vote model additionaly stores a category_id and a rating, so that you can give different rated votes in the same category. For the polymorphic many-to-many relation between vote and Teacher/Student i use a fourth model called SingelVotedRelation, which has the attributes vote_id, voted_id and voted_type.
Now i want to have the ability to call vote.voted and get a collection of the voted persons (student or teacher) without having to mess around manually with the SingleVotedRelation Table:
has_many :voted, through: :single_voted_relations
But i get error messages when i tried this. My working Vote Model looks like this:
belongs_to :voter, class_name: "Student"
has_many :single_voted_relations
has_many :voted_students, through: :single_voted_relations, source_type: "Student", source: :voted
has_many :voted_teachers, through: :single_voted_relations, source_type: "Teacher", source: :voted
SingleVotedRelation looks like this:
belongs_to :vote
belongs_to :voted, polymorphic: true
Student:
has_many :given_votes, class_name: "Vote", foreign_key: "voter_id"
has_many :single_voted_relations, class_name: "SingleVotedRelation", as: :voted # polymorphic
has_many :achieved_votes, source: :vote, through: :single_voted_relations
Teacher:
has_many :single_voted_relations, class_name: "SingleVotedRelation", as: :voted # polymorphic
has_many :achieved_votes, source: :vote, through: :single_voted_relations
I basically just want to join the voted_students and voted_teachers relations from the Vote-Model into one voted relation, which contains the students and also the teachers.
This may be too complicated and there could be a smarter way to achieve my goal.
Is it even possible to have different source types?
Help is appreciated
Thanks!

How do I set up a has many through for two different associations between the the same models

I have two models, a User and a Event, and I would to set up two different associations between them.
I want:
- a user to have many hosted events
- a user to have many attended events
- an event to belong to one user (owner / creator)
- an event to belong to many users (attendees)
It's a has and belongs to many relationship for the attended event and just has many for the hosted events, I just don't know how to set it up properly / the rails way.
I know I'd need a users_attended_events table
I think that would be something like this on the User model
has_many :events, through: :hosted_events
has_many :events, through: :attended_events
But what would I do about the Event's model?
I have:
belongs_to: user
alias_attribute :owner, :user
alias_attribute :creator, :user
has_many :users, through:???
this should be the users_attended_events table, so.. what would I put here? How do I name this "Attendees"
It doesn't sound like you need a has_many through association for users hosting events. Something like this should work sufficiently for that (in user.rb) if you have a hosted_by_id column on your events table:
has_many :hosted_events, class_name: "Event", foreign_key: "hosted_by_id"
For attending events assuming a join class with columns attendee_id and event_id:
class AttendeeEvent < ActiveRecord::Base
belongs_to :attendee, class_name: "User"
belongs_to :event
end
You can add the following association to user.rb:
has_many :attendee_events, foreign_key: "attendee_id"
has_many :attended_events, through: :attendee_events, source: :event
The source: :event option indicates that the target objects for this association are found from the event association on the joining object.
The associations in event.rb are then:
belongs_to :hosted_by, class_name: "User"
has_many :attendee_events
has_many :attendees, through: :attendee_events

rails associations with plural, possible, foreign keys

For example I have 2 players game, with future to have more than one game in the same time.
I need to create associations that will give me the opportunity to simply call user.games and it will return all games where that user is involved, as first_user_id or second_user_id.
table games
desk
first_user_id
second_user_id
table users
id
Model User
has_namy ??
What I think you are asking for:
class Game < ActiveRecord::Base
has_one :first_user, foreign_key: :first_user_id, class_name: 'User'
has_one :second_user, foreign_key: :second_user_id, class_name: 'User'
end
class User < ActiveRecord::Base
has_many :first_user_games, foreign_key: :first_user_id, class_name: 'Game'
has_many :second_user_games, foreign_key: :second_user_id, class_name: 'Game'
def games
first_user_games + second_user_games
end
end
I think it is probably a bad sign that you need this though, and it will be a pain. Instead you could just add a UserGames table and UserGame join model, put a column called "player_number" on UserGame, and do has_many :games, through: :user_games on User to get all games, regardless of player number, and then use queries, filtering, scoping, etc. as needed to find only the games where the player was a certain number. Here are a few questions related to scoping/conditions for has_many ..., through: ... on columns in the join model, in case you want to be able to access lists of games in User where the player is a specific player #:
Rails complex has_many and has_many through
Scope with join on :has_many :through association

What inverse_of does mean in mongoid?

What inverse_of does mean in mongoid associations? What I can get by using it instead of just association without it?
In a simple relation, two models can only be related in a single way, and the name of the relation is automatically the name of the model it is related to. This is fine in most cases, but isn't always enough.
inverse_of allows you to specify the relation you are referring to. This is helpful in cases where you want to use custom names for your relations. For example:
class User
include Mongoid::Document
has_many :requests, class_name: "Request", inverse_of: :requester
has_many :assignments, class_name: "Request", inverse_of: :worker
end
class Request
include Mongoid::Document
belongs_to :requester, class_name: "User", inverse_of: :requests
belongs_to :worker, class_name: "User", inverse_of: :assignments
end
In this example, users can both request and be assigned to tickets. In order to represent these two distinct relationships, we need to define two relations to the same model but with different names. Using inverse_of lets Mongoid know that "requests" goes with "requester" and "assignments" goes with "worker." The advantage here is twofold, we get to use meaningful names for our relation, and we can have two models related in multiple ways. Check out the Mongoid Relations documentation for more detailed information.

Resources