In a many to many relationship, it’s just a table between the entities, but what is the purpose of this table to be between them?
Let’s explain it with an example, in Rails we can create two models and these 2 models can be connected just with a foreign key by Active Record Associations.
rails g model User name
rails g model Event name user_id:integer
Now that we have created these 2 models (Don’t forget to migrate the models with rails db:migrate) Event and User, how do we connect them? Well, the user_id is going to be our foreign key, remember that primary keys are unique between models, so in order to reference our User model. We can start saying “A User can have many events but an Event belongs to one single user”. Therefore, to make that happen we need to use the reserved words has_many and belongs_to like this:
class User < ApplicationRecord has_many :events end
class Event < ApplicationRecord belongs_to :user end
Great! Now we can start playing with our associations and Rails will make 2 assumptions :
1) The class of the model your association points to is based directly off of the name of the association.
2) The foreign key in any belongs_to relationship will be called yourassociationname_id.
In our Rails console we can now do something like this:
user = User.new; user.name = "Fernando" user.save event = Event.new event.name = "The big Party" event.user = user
Ok, so we created a new User called “Fernando” and we created a new Event called “The big Party” thanks to our belongs_to we can use
and tell that specific event that it belongs to that specific user. So after we save our event with
We can get our user for that specific event! as an Active Record with
and it will return that specific user for that specific event
#<User id: 1, name: "Fernando", created_at: "2020-10-06 17:14:30", updated_at: "2020-10-06 17:14:30">
Now to get all the events that this User has we can do
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 1, name: "The biggest party", user_id: 1, created_at: "2020-10-06 17:33:01", updated_at: "2020-10-06 17:33:01">]>
Do you see the difference? The difference is that in one query we received an Active_Record while on the other query related to the events we received a CollectionProxy, CollectionProxy is nothing more than an array since a user can have multiple events, but remember an event can have only 1 User.
Now let’s complicate things a little bit more…
Imagine that you want your “users to attend many events, but only one user to be the owner for that specific event?” We can think in a way to create another model called
, and do another relation between
but what will be the difference between
? They both will have the same attributes (Invitee,User) the main difference is that one is attending the event and the other one isn’t? So we need to think in a way to use our model User to be distinguished as an “Owner” and as an “Invitee”.
So we can say something like this: “A User can have many events and can attend many events” and “An Event can have many attendees but it only belongs to one single user “. We can now see clearly that we have a Many/Many relationship between these 2 models. So what approach should we take?
First, we need 2 Foreign-Keys one for the Owner and one for the Attendee, we can add a new column to our Event model with the command:
rails g migration add_column_owner_id_to_events owner_id:integer
Don’t forget to migrate it with
Ok, now that we have a new column called
we need to specify that instead to look for
to look for a column called
foreign_key to identify that owner_id for that user is meant to be the host of that event.
class User < ApplicationRecord has_many :events, foreign_key: "owner_id", class_name: "Event" has_many :attending_events, class_name: "Event" end
Note here that I changed to
to clarify that he is attending to many events but in order to do that we need to specify the class_name, and it will go ahead and look by default user_id foreign_key into the Event Model. So if we type
we will be getting the events that the first user is attending into our Rails App.
Now we need to create some owners for those events, lets create 3 more users as we did before and also 1 more event. If you did everything right … You should now have 3 Users. Now let’s assign those events an owner.
ev1 = Event.first ev2 = Event.second ev1.owner = User.first ev2.owner = User.second # Update the events with the **save** method ev1.save ev2.save
class Event < ApplicationRecord belongs_to :owner, class_name: "User" has_many :attendees,foreign_key: "id", class_name: "User" end
Now we can see the owner for each event by saying for the first event :
it will return us the ActiveRecord for that specific event, that’s because we are saying to Rails to look for on
and locate that
in User class, but now how can we see the attendees for that specific event? We can just type
and Rails will go to the User class and match with
since we specificied on
, else it would be looking for a field called event_id.
Now, let’s analyze some problems that we have here … For example, if we create a new Attendee for the same event, we will need to create the same event but with a different user_id on the Event model, imagine if we have 1,000 attendees we will have 1,000 event instance of the same event, but with different user_ids on the same model making it impossible to query it!
What should we do?
Yes, a Through table is what we need, to keep our attendees in a separate table and to leave our unique Event in its own model. So how does it works?
First let’s get one thing straight… Let’s remove the
from Event model with :
rails g migration remove_user_id_from_events user_id:integer
and again.. Don’t forget to migrate it with
(at this point im going to skip to say rails db:migrate every time we type a command on the terminal )now we have our Event model without the user_id column, remember! that was our field to take our attendees.
Let’s generate our new model to work as a communication between User & Event. By conversion this table is called by the name of the 2 models (User & Event) concatenated. So it can be called EventUser, UserEvent. I’m going to call it EventAttendee, since we want to show all of our attendees for a specific event. We generate our new model as before with:
rails g model EventAttendee event_id:integer attendee_id:integer
Now we set up our through table saying that the
will belong to Event model and that
will belong to User model :
class EventAttendee < ApplicationRecord belongs_to :event, class_name: "Event" belongs_to :attendee, class_name: "User" end
Now, let’s configure our User model to communicate with our EventAttendee through table.
class User < ApplicationRecord has_many :events, foreign_key: "owner_id", class_name: "Event" has_many :event_attendees, foreign_key: "attendee_id" has_many :attending_events, through: :event_attendees, source: :attendee end
Ok.. Let’s explain what we did here…
has_many :event_attendees, foreign_key: "attendee_id"
we are saying that a “A user can attend many events” and to search it by
has_many :attending_events, through: :event_attendees, source: attendee
the name of the method will be be
so we can use it as E.G: User.first.event_attendees and look it through
attribute? that attribute is neccessary because if not we are going to look for a column called
but in our model EventAttendee our column is called
, so that’s why we specify
to go look for
but the method name is called
Now let’s configure our Event model to communicate with our EventAttendee through table.
class Event < ApplicationRecord belongs_to :owner, class_name: "User" has_many :event_attendees, foreign_key: "event_id" has_many :attendees, through: :event_attendees, source: :event end
Let’s explain again but now with Event model
has_many :event_attendees, foreign_key: "event_id"
we are saying“A event can have many users(attendees)” that’s why we are calling our method
so we can use it as E.G: Event.first.attendees and to look it through
model, since our method is called
we specify with source: to look for event_id with
Now we can see each of our events attendees, events owners, the events that the user is attending, and the infrastructure is pretty well organized that let us query single events for the Event model. Just by adding another table through our model in our many to many relationship…