Skip to content

Instantly share code, notes, and snippets.

@kcurbelo
Last active November 26, 2024 22:33
Show Gist options
  • Save kcurbelo/a1395d64b5a01fb56b03 to your computer and use it in GitHub Desktop.
Save kcurbelo/a1395d64b5a01fb56b03 to your computer and use it in GitHub Desktop.
Explaining a has_many :through association.

has_many :through


In this exercise we are going to explore the has_many through relationship by creating a quick example. The first part of this markdown will be geared toward making a simple example that shows the relationship while the second part will consist of expanding the simple app to show the relationships in the browser.

We are going to use doctors and patients for this example.

As you could have guessed a doctor can have many patients and a patient can have many doctors. One way that we could facilitate this many to many relationship is by using an "associative table". We can call our associative table "appointments", which should help us make it easier to understand the relationship between doctors and patient.

An appointment can only have one doctor and one patient at a time but both doctors and patients can have many appointments. Also a doctor and patient meeting(association) only happen when they have an appointment.

Lets start to code up this example starting up a new project that we can play around with. We can set it up using postgreSQL or sqlite.

If you want to set up your project with postgreSQL:

rails new many_to -d postgresql

or if you would like to set it up using sqlite:

rails new many_to

I've called mine many_to but you could call yours whatever you would like.

Make sure to cd into the folder:

cd many_to

Now lets create the models that we will need in order to demonstrate this example. We are going to need three in total, one for our Doctor model, one for our Patient model, and one for our Appointment (associative) model.

Create a Doctor model with "name" and "email", so that way when we create out database these two fields will populate our database table:

rails g model Doctor name email

Create a patient model with "name", "email", and "age:integer". (We have used integer with age because the default is string but we want a number for age not a string):

rails g model Patient name email age:integer

Create a patient model:

rails g model Appointment

Although each model has been created there will be no relationships inside of them so we will have to go in and add them. We will add the code in and then we can talk about what is going on with the relationships.

Open your project in your preferred text editor

Like the models, we will start with the Doctor model. Inside of the doctor model add the relations:

class Doctor < ActiveRecord::Base
  has_many :appointments
  has_many :patients, through: :appointments
end

Next is the Patient model:

class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :doctors, through: :appointments
end

Finally is the Appointment model:

class Appointment < ActiveRecord::Base
  belongs_to :doctor
  belongs_to :patient
end

Let's take a quick moment to explain what we have done.

In the Doctor model we are saying that the doctor has many appointments and at the same time has many patients but only because he/she has appointments. A doctor can't have any patients without there being an appointment to bring the them together.

In the Patient model we are saying the same thing. A patient can have many appointments as well as having multiple doctors (i.e eye doctor, general physician, etc). A patient cannot see any doctor without having an appointment.

In the Appointment model we are saying that appointments only belong to a doctor and a patient. One appointment can only have one doctor and one patient at a time. One appointment cannot have many doctors or have many patients.

Even just thinking about when you visit the doctor this still holds true. When you see a doctor for something, you must make sure you have an appointment or else you can't see the doctor. Once you get to the doctors office for your appointment, you only see one doctor and you are the only patient he is seeing at that time. Even if you need to see another doctor that day, you (or the doctor/nurse) have to make another appointment with the other doctor. So all in all the only way that you saw the doctor was through the appointment you had set up.

We will need to add one last snippet of code to fully link up the relations. Going into the migration folder, you should see a file with a bunch of numbers followed by create_appointments.rm (something like "20150217211405_create-appointments.rb").

Then we are adding in:

      t.belongs_to :doctor, index: true
      t.belongs_to :patient, index: true

Which should look like:

class CreateAppointments < ActiveRecord::Migration
  def change
    create_table :appointments do |t|
      t.datetime :appointment_date

      t.belongs_to :doctor, index: true
      t.belongs_to :patient, index: true

      t.timestamps null: false
    end
  end
end

If you set up your project with postgreSQL you will have to create database before you migrate to it (if you do this step and your project was started using sqlite, no need to worry because it won't effect anything):

In the command line:

rake db:create

In order to create these models in our database we will need to do a rake:db migrate.

rake db:migrate

Now we can navigate into our "db" folder and check out our schema.

It should look something like this:

ActiveRecord::Schema.define(version: 20150217211405) do

  create_table "appointments", force: true do |t|
    t.datetime "appointment_date"
    t.integer  "doctor_id"
    t.integer  "patient_id"
    t.datetime "created_at",       null: false
    t.datetime "updated_at",       null: false
  end

  add_index "appointments", ["doctor_id"], name: "index_appointments_on_doctor_id"
  add_index "appointments", ["patient_id"], name: "index_appointments_on_patient_id"

  create_table "doctors", force: true do |t|
    t.string   "name"
    t.string   "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "patients", force: true do |t|
    t.string   "name"
    t.string   "email"
    t.integer  "age"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

There we have it, the table are all linked together!



Optional

If you would like we could continue on with this example to see how all of this information can be shown.

Let's add some data into out tables. First we will need to fire up our rails console.

rails c

Now we can copy and paste some dummy data for patients, doctors, and appointments.

For patients:

p1 = Patient.new(name:"Bob", email:"[email protected]", age:22)
p1.save

p2 = Patient.new(name:"Bond", email:"[email protected]", age:25)
p2.save

p3 = Patient.new(name:"Marco", email:"[email protected]", age:45)
p3.save

p4 = Patient.new(name:"Archer", email:"[email protected]", age:30)
p4.save

p5 = Patient.new(name:"Lana", email:"[email protected]", age:32)
p5.save

For doctors:

d1 = Doctor.new(name:"Nick", email:"[email protected]")
d1.save

d2 = Doctor.new(name:"Hibbert", email:"[email protected]")
d2.save

d3 = Doctor.new(name:"Zoidberb", email:"[email protected]")
d3.save

For appointments:

a1 = Appointment.new(doctor_id:1, patient_id:1)
a1.save
a2 = Appointment.new(doctor_id:1, patient_id:2)
a2.save
a3 = Appointment.new(doctor_id:1, patient_id:3)
a3.save
a4 = Appointment.new(doctor_id:1, patient_id:4)
a4.save
a5 = Appointment.new(doctor_id:2, patient_id:5)
a5.save

Normally this data would have been generated from a form or some kind of input on a page in your web site but for this example we will just do it from the console.

Let's add a controllers and views for our doctors, so we can really see this relationship in action.

The command for that is:

rails g controller doctors show

We are generating our doctors controller with the show method attached which will have the method available to us in the controller but also create the corresponding view.

Going into out doctors controller we can add in:

class DoctorsController < ApplicationController
  def index
  end

  def show
  	 @d1 = Doctor.find(1)
  	 @d2 = Doctor.find(2)
  	 @d3 = Doctor.find(3)
  end

end

This code is saying that make available the doctor with the id of 1, 2, and 3 by finding the doctors with those ids attached to them.

Next we will need to make sure we have a place to show those doctors, so we will need to add some code to our show.html.erb.

In the show.html.erb:

<!DOCTYPE html>
<html>
<head>
	<title>Example</title>
</head>

<body>

<!-- Demonstration information -->
  <h1>Doctor's page showing patients</h1><hr />

  <p>This is all the information available for "@d1":<p>
  <%= @d1.inspect %><hr />

  <p>This is all the information available for all patients of "@d1":<p>
    <%= @d1.patients.inspect %>
  <hr />

  <p>This is all the information available for patient in the first position(in the collection):<p>
    <%= @d1.patients[0].inspect %>
  <hr />
<!-- Doctor 1's name and patients -->
  <h2>Doctor Name: <%= @d1.name %></h2>
  <h3>Doctor <%= @d1.name %>'s patients:</h3>

    <%= @d1.patients[0].name %><br />
    <%= @d1.patients[1].name %><br />
    <%= @d1.patients[2].name %><br />
    <%= @d1.patients[3].name %><br />
  <br />
  <hr />
<!-- Doctor 2's name and patients -->
  <h2>Doctor Name: <%= @d2.name %></h2>
  <h3>Doctor <%= @d2.name %>'s patients:</h3>
    <%= @d2.patients[0].name %><br />


</body>

</html>

In the code above we have added some information to the show page that will let us see how we can pull information from different tables and show there information on one page.

The last bit of code we need is for our routes so we can make sure we are going to the right place.

In the routes.rb and replacing any other routes you have, we put:

  resources :doctors
  root 'doctors#show'

Although "resources :doctors" will generate many more routes than we will need for the scope of this example, We have use it for simplicity. We have also included "root" which will be the first page that is show when we visit the site.

Time to see all that hard work.

We will need to start our rails server and navigate to the doctors show url (or just the home page).

For the server: rails s

What to paste in the url:

http://localhost:3000/doctors/show

or

http://localhost:3000/

There it is! Hope that you enjoyed this little example and that you gained some more understanding of how the associations are made.


Contributors:

Grant Roy

Zach Johnson

Resources:

http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment