Has Many Through Relation?
Has many through, what? What do you mean by many through? Why do I need that? Did you say scopes?
Today I will explain to you more about the has many through relations and scopes in rails. I will use an example with events where a user can create events and also attend events!
First of all, let’s start by creating a simple User model with just a name.
$ rails g model User name
Now we got our User model, let’s create our Event model.
$ rails g model Event description date:datetime user:references
So our Event model got now a description about the event and place, a date to know when the event will take place, and the user references just so we know who created that event! Let’s connect our models, so that is already out of the way! Navigate to app/models/user.rb.
app/models/user.rbclass User < ApplicationRecord has_many :eventsend
Ok, so a single user can create many events. So we write “has_many :events”. If we go to our Event model now in app/models/event.rb, we can see that rails already added “belongs_to :user” in our model, that is because when we created the Event model we wrote “user:references”, and as we all know, Rails is almost as smart as Stephen Hawking, so he already wrote it there for us!
Great, so we got our User model and Event model, our user can create events, and now we need to find a way how our user can also attend events… but how? This is the moment when we need a third table! So we need a table that will show the user that will attend the event and which event he will attend.
So let’s finish of by creating our third model which we will be using as our through table to know which user will attend which event. So we need to add two things in this model the user who will attend the event, and the event the user will be attending! As this will be our last model we might just migrate our database as well!
$ rails g model UserEvent user:references event:references$ rails db:migrate
Ok, this is getting confusing! We already have our user references in the Event table, so why do we also need it in our UserEvents table? Like I mentioned before we added the user references into the Event model so we know who created the events. But we also need to know which event he will be attending, as a user can create but also attend events. Great, so I’m still confused! with all those user references everywhere. Let’s check out our new model and let’s connect it with our other tables. Let’s navigate to our new model! app/models/user_event.rb
app/models/user_event.rbclass UserEvent < ApplicationRecord belongs_to :user
belongs_to :eventend
Ok, so once again Rails already wrote everything for us! Cool, now we just have to add our connection in our User and Event model! Let’s go first to our User model. app/models/user.rb
app/models/user.rbclass User < ApplicationRecord has_many :events has_many :user_events
has_many :events, through: :user_eventsend
Ok, what’s happening here? So first of all we add “has_many :user_events”, because a user can attend as many events as he wants, right? Of course, this was only before the pandemic, just kidding! So let’s check out that next line where we wrote “has_many :events, through:”. So we need to know which events the user will be attending, as the event is in our user_events table. But wait we got two times “has_many :events”, It would be impossible to know about which events we are talking even for Stephen Hawking. So we can modify either the first or second “has_many :events”, we will use the second one (The last one).
app/models/user.rbclass User < ApplicationRecord has_many :events has_many :user_events has_many :attending_events, through: :user_eventsend
So we changed “events”, to “attending_events”. That looks already much easier to understand about what we are talking when we call “attending events”! But we don’t have any “attending_events_id” in our third table. So we need to tell rails about which model we are actually talking! So let’s add this at the end of the line.
app/models/user.rbclass User < ApplicationRecord has_many :events has_many :user_events has_many :attending_events, through: :user_events, source: :eventend
Now we told rails that when we ask for “attending events” we actually mean just “events”, but in our “user_events” table. Ok, that sounds still confusing maybe we could just change our column name from “event_id” to “attending_event_id”. Let’s create our migration file!
$ rails g migration ChangeEventIdToAttendingEventId
Now we need to modify our migration file which we can find in this location. db/migrate/***********_change_event_id_to_attending_event_id.rb
db/migrate/***********_change_event_id_to_attended_event_id.rbclass ChangeEventIdToAttendingEventId < ActiveRecord::Migration[6.1] def change rename_column :user_events, :event_id, :attending_event_id endend
So, when we rename a column, we should always put first the table, where we want to change a column, then the name of the column we want to change, and last but not least, the name of the new column we want to change it to.
rename_column :table, :old_column, :new_column
Let’s now migrate it and take a look in our schema what happened.
$ rails db:migrate
Lets navigate to db/schema.rb.
db/schema.rb
ActiveRecord::Schema.define(version: 2021_05_20_110733) docreate_table "user_events", force: :cascade do |t| - - -end - - -add_foreign_key "events", "users"add_foreign_key "user_events", "events", column: "attending_event_id"
add_foreign_key "user_events", "users"end
Like you can see Rails aka Stephen Hawking just added the “event_id” but named the column “attending_event_id”.
Great! Now we are ready with our user_events table. Let’s take a look at our Event model. Navigate to our app/models/event.rb file, and do the same as we did in our user.rb file. We also need to tell that our Event model will be using the “attending_event_id”, instead of the “event_id”.
app/models/event.rbclass Event < ApplicationRecord belongs_to :user has_many :user_events, foreign_key: "attending_event_id"
has_many :users, through: :user_eventsend
Alright, what do we’ve got here? Belongs to User and has many users… Not sure which one to change, as they both look quite confusing, right? You know what? Let’s change both of them! Let’s start with our belongs to. We could change it to “creator” instead of “user”. So let’s change that line.
app/models/event.rbclass Event < ApplicationRecord belongs_to :creator, class_name: "User" has_many :user_events
has_many :users, through: :user_eventsend
As we have no creator model, we should specify the class name we are trying to target, in our case “User”. Reminder alert! Always write it in the singular and capitalized.
Great, so maybe we should change our column in our events table from “user_id” to “creator_id”. Let’s create our migration file!
$ rails g migration ChangeUserIdToCreatorId
Let’s navigate to our migration file, btw the newest file is always the last file in our migration folder! db/migrate/*******_change_user_id_to_creator_id.rb.
db/migrate/******_change_user_id_to_creator_id.rbclass ChangeUserIdToCreatorId < ActiveRecord::Migration[6.1] def change rename_column :events, :user_id, :creator_id endend
Just like before we first write the table name, followed by the column we want to rename, and finally the new name of the column we want to change it to.
Let’s migrate our database.
$ rails db:migrate
Now we need to tell our User model that he will be using the “creator_id” when looking for events. Let’s add this to our User model.
app/models/user.rbclass User < ApplicationRecord has_many :events, foreign_key: "creator_id" has_many :user_events has_many :attending_events, through: :user_events, source: :eventend
So we will tell that the column name in events will be “creator_id”. Great! That’s already looking splendid! Let’s go back to our Event model!
We should change the “has_many :users” to “has_many :attendees”, as it sounds much better, right? Let’s open the Event model by navigating to app/models/event.rb.
app/models/event.rbclass Event < ApplicationRecord belongs_to :creator, class_name: "User" has_many :user_events, foreign_key: "attending_event_id" has_many :attendees, through: :user_events, source: :userend
Great so let’s change our “user_id” in our user_events table to “attendee_id”. Another migration…
$ rails g migration ChangeUserIdToAttendeeId
Let’s navigate to our db/migrate/*******_change_user_id_to_attendee_id.rb file.
db/migrate/******_change_user_id_to_attendee_id.rbclass ChangeUserIdToCreatorId < ActiveRecord::Migration[6.1] def change rename_column :user_events, :user_id, :attendee_id endend
Now let’s migrate it!
$ rails db:migrate
And we also need to tell that our user model will be using the “attendee_id” instead of the “user_id”. So, let’s navigate to our app/models/user.rb file and add the foreign key we will be using for the User model in our user_events table.
app/models/user.rbclass User < ApplicationRecord has_many :events, foreign_key: "creator_id" has_many :user_events, foreign_key: "attendee_id" has_many :attending_events, through: :user_events, source: :eventend
Great! Our last step would be to also tell our UserEvent model what we did! So let’s navigate to our app/models/user_event.rb file, and add the foreign keys to each belongs to relationship.
app/models/user_event.rbclass UserEvent < ApplicationRecord belongs_to :user, foreign_key: "attendee_id" belongs_to :event, foreign_key: "attending_event_id"end

Now we got all the models connected to each other with the right names that we want to use! Let’s show of our hard work now! Open the rails console by typing.
$ rails c
Let’s create three users. One that will create the event and two others that we will invite to our event.
irb(main):001:0> john = User.create(name:"John")
irb(main):002:0> james = User.create(name:"James")
irb(main):003:0> lucas = User.create(name:"Lucas")
Great! Now let’s create an event with John as the creator.
irb(main):004:0> event = john.events.create(description: "Star Wars costume party at 900 Ocean Dr, Miami Beach, FL 33139, United States", date:"05-06-2021")
Great now we need to invite John’s two best friends James and Lucas.
irb(main):005:0> event.user_events.create(attendee_id: james.id)
irb(main):006:0> event.user_events.create(attendee_id: lucas.id)
Great so now we invited both friends of John, let see how useful everything was we wrote in our models. Let’s see if John really is the creator of our event.
irb(main):007:0> event.creator
=> #<User id: 1, name: "John", created_at: "2021-05-20 12:37:58.028578000 +0000", updated_at: "2021-05-20 12:37:58.028578000 +0000">
Ok, we are getting the user model back, so you can write…
irb(main):008:0> event.creator.name
=> "John"
Great! That will be already much easier when we have to find the creator of our event! Let’s test out the rest of the relations we implemented before!
irb(main):009:0> event.attendees
This should return to you all the attendees that are attending the event! Ok, one more thing! Where can I find which event I’m attending? Let’s try that out!
irb(main):010:0> james.attending_events
This returns all the events I’m attending! That was not so difficult! We only had to make a few adjustments to our models and tables and now we are able to use those queries!
The only problem we have is that when we write “james.attending_events”, we don’t really know if it’s an upcoming event or past event…
That’s where the scope becomes useful, so let’s navigate to our Event model, and write the following!
app/models/event.rbclass Event < ApplicationRecord belongs_to :creator, class_name: "User"
has_many :user_events, foreign_key: "attending_event_id"
has_many :attendees, through: :user_events, source: :user scope :upcoming_events, -> { where("date > ?", Date.today)} scope :past_events, -> { where("date < ?", Date.today)}end
So, when creating a scope you first write the name of the scope, followed by an arrow that points at our condition. In our first scope, we create an upcoming_events scope that will check if the date is greater than the date today. In our second scope, we do the opposite. Let’s see if our scope is working. Open up your rails console by writing.
$ rails c
Now we need to find our users back as we closed our rails console and they are not assigned anymore to our variables.
irb(main):001:0> john = User.find_by(name:"John")
irb(main):002:0> james = User.find_by(name:"James")
irb(main):003:0> lucas = User.find_by(name:"Lucas")
Great! We already have an upcoming event so let’s create a past event! Of course, this depends on when you read this article, so change the date accordingly!
irb(main):004:0> john.events.create(description: "Party from last week on the beach next to my house", date: "05-05-2021")
Now we have an upcoming event and a past event! Let’s test if our scopes are working!
This should only output the first event we’ve created.
irb(main):005:0> john.events.upcoming_events
This should only output the last event we’ve created.
irb(main):006:0> john.events.past_events
Yay, we know how to use scopes!
Other examples you should try!
irb(main):007:0> event = Event.last
irb(main):008:0> event.user_events.create(attendee_id: lucas.id)
irb(main):009:0> lucas.attending_events.upcoming_events
irb(main):010:0> lucas.attending_events.past_events
Today we’ve learned the “most used” associations! To get a better understanding about the belongs_to and has_many, read this article…
So we made it till the end, and are now more confident in creating apps with more complex databases! I hope you guys enjoyed this article as much as I did, but the most important part is that you’ve learned something new today!