Associating with Ruby on Rails

What is Association

all about?

Accessing info from Database B via Database A.

And vice versa.

A.B.attribute

and/or

B.A.attribute

Types of Association

One-to-One:

The simplest form of Association, where one instance of a Resource "has one" of the other, and that instance of the other resource "belongs to" one of the first

One-to-Many:

Where one instance of a Resource "has many" of the other, and those instances of the other resource only "belong(s) to" that instance of the first.

Many-to-Many:

The hard one! Where instances of a Resource can "have (has) many" instances of the other Resource, and many instances of that Resource can "have (has) many" right back. But this type of Association will require a 3rd DB: the Join Table!

One-to-one Association

Let's create a one-to-one association, between two databases: Driver & Vehicle

$ rails g scaffold Driver name:string
$ rails g scaffold Vehicle make:string model:string driver_id:integer

Notice the driver_id attribute of the Vehicle!

This is called the "Foreign Key" (FK). It relates to the ID number of a Driver in its own database.

 

Warning! If you're using any other name for your first DB than "Driver", make sure the _id attribute in Vehicle matches!

(e.g., pilot_id, owner_id, etc.)

Association Set-up, steps 2 & 3

The next two steps to setting up an association are defining the relationship in the respective models.

driver.rb

vehicle.rb

class Vehicle < ActiveRecord::Base

	belongs_to :driver

end
class Driver < ActiveRecord::Base

	has_one :vehicle
	
end

Drivers have one Vehicle

Vehicles belong to one Driver

And now we're associated!

That's really all it takes to do a one-to-one association!

The trick now is to make the calls between the databases...

who's driving this thing anyway?

In our Vehicle _form.html.erb, we want to pull in a list of our Drivers, to assign their IDs to the new (or updated) Vehicle.

First, we need to pull in the Drivers data in the vehicle_controller.rb:

def new
  @vehicle = Vehicle.new
  @drivers = Driver.all
end

def edit
  @drivers = Driver.all
end

who's driving this thing anyway?

Now we can create a dropdown menu to select Drivers from, by name!

In vehicles/_form.html.erb:

  <div class="field">
    <%= f.label :make %><br>
    <%= f.text_field :make %>
  </div>
  <div class="field">
    <%= f.label :model %><br>
    <%= f.text_field :model %>
  </div>
  <div class="field">
    <%= f.label :driver_id %><br>
    <!-- The line below is our new bit of code: -->
    <%= f.select(:driver_id, @drivers.collect {|d| [ d.name, d.id ] }, {:include_blank => 
     'Please select a item'}) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>

choose your driver

Now we have a nice dropdown selection tool:

We see the name, but it's the ID that's passed!

show your associations!

Let's edit the display pages (index & show) of the two resources to visualize the association.

Start with the show pages:

vehicles/show.html.erb

drivers/show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Make:</strong>
  <%= @vehicle.make %>
</p>

<p>
  <strong>Model:</strong>
  <%= @vehicle.model %>
</p>

<!-- We'll display the Driver's name instead of the ID-->
<!-- First, set up a safeguard against nil -->
<% if @vehicle.driver != nil %>
	<p>
	  <strong>Driver:</strong>
	  <!-- display the Driver's name thru assoc! -->
	  <%= @vehicle.driver.name %>
	</p>
<% end %>

<%= link_to 'Edit', edit_vehicle_path(@vehicle) %> |
<%= link_to 'Back', vehicles_path %>
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @driver.name %>
</p>

<p>
  <strong>City:</strong>
  <%= @driver.city %>
</p>

<!-- We'll display the Driver's Vehicle -->
<!-- First, set up a safeguard against nil -->
<% if @driver.vehicle != nil %>
	<p>
	  <strong>Vehicle:</strong>
	  <!-- display the Vehicle's make & model 
               thru assoc! -->
	  <%= "#{@driver.vehicle.make} 
            #{@driver.vehicle.model}" %>
	</p>
<% end %>

<%= link_to 'Edit', edit_driver_path(@driver) %> |
<%= link_to 'Back', drivers_path %>

show your associations!

Now we can edit the index pages:

(I'm showing just the tables from these pages)

vehicles/index.html.erb

drivers/index.html.erb

<table>
  <thead>
    <tr>
      <th>Make</th>
      <th>Model</th>
      <!-- We'll display the Driver's name instead of the ID-->
      <th>Driver</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @vehicles.each do |vehicle| %>
      <tr>
        <td><%= vehicle.make %></td>
        <td><%= vehicle.model %></td>
        <td>
          <!-- set up a safeguard against nil -->
          <% if vehicle.driver != nil %>
            <!-- display the Driver's name thru association! -->
            <%= vehicle.driver.name %>
          <% end %>
        </td>
        <td><%= link_to 'Show', vehicle %></td>
        <td><%= link_to 'Edit', edit_vehicle_path(vehicle) %></td>
        <td><%= link_to 'Destroy', vehicle, method: :delete, 
              data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>City</th>
      <!-- We'll display the Driver's Vehicle -->
      <th>Vehicle</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @drivers.each do |driver| %>
      <tr>
        <td><%= driver.name %></td>
        <td><%= driver.city %></td>
        <td>
          <!-- set up a safeguard against nil -->
          <% if driver.vehicle != nil %>
          <!-- display the Vehicle's make & model thru assoc! -->
            <%= "#{driver.vehicle.make} #{driver.vehicle.model}" %>
          <% end %>
        </td>
        <td><%= link_to 'Show', driver %></td>
        <td><%= link_to 'Edit', edit_driver_path(driver) %></td>
        <td><%= link_to 'Destroy', driver, method: :delete, 
              data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
</table>

I can see your associations!

One-to-many association

A One-to-Many Association is very similar to the One-to-One.

There's a just a slight change of verbiage.

 

But we'll start off the same.

 

Let's associate States with Cities...

We still have one Resource taking the Foreign Key (ID) of the other:

state_id

$ rails g scaffold State name:string
$ rails g scaffold City name:string state_id:integer

"One-to-many" set-up, Steps 2 & 3

Once again we will define the relationship

between the resources in the respective models.

state.rb

city.rb

class City < ActiveRecord::Base

	belongs_to :state

end
class State < ActiveRecord::Base

	has_many :cities

end

State has many Cities

(instead of has_one)

A City belongs to one State

(same as 1-to-1 assoc.)

Selecting States

Let's set up the State selection in the City

form just as we did with Driver/Vehicle.

cities/_form.html.erb

cities_controller.rb

def new
  @city = City.new
  @states = State.all
end

def edit
  @states = State.all
end
  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :state_id %><br>
    <!-- Copy the select tag from the
    Vehicles form and adjust it to 
    fit our needs here... -->
    <%= f.select(:state_id, 
      @states.collect {|s| [ s.name, s.id ] }, 
      {:include_blank => 'Please select 
      an item'}) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>

Choose Your state

Voila! A nice dropdown menu to choose our city's state!

Display your States!

Showing the City, State association will be about the same as the Vehicle/Driver association.

cities/index.html.erb

cities/show.html.erb

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>State</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @cities.each do |city| %>
      <tr>
        <td><%= city.name %></td>
        <!-- we only need to switch this line
            from city.name_id to: -->
        <td><%= city.state.name %></td>
        <td><%= link_to 'Show', city %></td>
        <td><%= link_to 'Edit', edit_city_path(city) %></td>
        <td><%= link_to 'Destroy', city, method: :delete, data: 
              { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<p id="notice"><%= notice %></p>

<!-- On this page, all we have to do is 
     display the city name and the state name,
     preferably side-by-side, with a comma between
     as is the preferred style -->

<p>
  <strong>Welcome To...</strong>
  <%= @city.name %>, <%= @city.state.name %>
</p>

<%= link_to 'Edit', edit_city_path(@city) %> |
<%= link_to 'Back', cities_path %>

I'm from the great state of...

localhost:3000/cities

localhost:3000/cities/1

What cities are in that state?

Showing the State/Cities association will be a bit trickier.

The cities associated with a State are collect in an Array.

states/show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>The Great State of... </strong>
  <%= @state.name %>
</p>

<p>
	<strong>Major Cities:</strong>
	<ul>
		<!-- We'll loop through the array of cities
		    and present them as an unordered list -->
		<% @state.cities.each do |city| %>
			<li><%= city.name %></li>
		<% end %>
	</ul>
</p>
<%= link_to 'Edit', edit_state_path(@state) %> |
<%= link_to 'Back', states_path %>

Cities in our state

localhost:3000/states/1

Displaying cities in the state INdex

Once again, we'll have to loop through an array of cities, but this time let's present them in a one-line list...

states/index.html.erb

<table>
  <thead>
    <tr>
      <th>Name</th>
      <!-- We add a header for the cities column -->
      <th>Cities</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @states.each do |state| %>
      <tr>
        <td><%= state.name %></td>
        <!-- We add a table cell that will display the cities within the state-->
        <td>
          <!-- In here we'll loop through the array of cities, printing them out with a comma between 
               (the last city in the array will not have a comma after it) -->
          <% state.cities.each_with_index do |city, index| %>
            <% if index < (state.cities.length - 1) %>
              <%= city.name %>,
            <% else %>
              <%= city.name %>
            <% end %>
          <% end %>
        </td>
        <td><%= link_to 'Show', state %></td>
        <td><%= link_to 'Edit', edit_state_path(state) %></td>
        <td><%= link_to 'Destroy', state, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Cities in our State, Index-style

localhost:3000/states

Many-to-many Association

The Many-to-Many association is the trickiest of the bunch.

Neither of the two main scaffolds will include an FK attribute.

A third scaffold will be needed, and it will only have FK attributes.

This is the Join Table.

We'll explore Many-to-Many using a Doctor/Patient relationship, with Appointment being the JoinTable.

$ rails g scaffold Doctor name:string
$ rails g scaffold Patient name:string
$ rails g scaffold Appointment doctor_id:integer patient_id:integer

Setting up the Models

In the Many-to-Many case, both Doctors and Patients have many appointments.

But they also have many of each other, but only through Appointments.

Finally, Appointments belong to both Doctors & Patients.

We'll start with...

appointment.rb

class Appointment < ActiveRecord::Base

	belongs_to :doctor
	belongs_to :patient

end

Setting up the models

Now let's see the other two models, and how exactly this through relationship takes shape.

doctor.rb

patient.rb

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

	has_many :appointments
	has_many :patients, :through => :appointments

end

Wait...that's it?

As before, setting up the Association is kinda easy. Include a FK here, write some code in a model, and PRESTO!, you got yourself some Associations.

The tricky park comes when you want to display the results of those Associations.

Get yourself some data

First, let create several Doctors and Patients, so we have some data to work with.

localhost:3000/doctors

localhost:3000/patients

Making Appointments

Before we start creating appointments, to associate our Doctors & Patients, let's set it up so that we choose from Doctor & Patient names, rather than having to supply ID numbers.

def new
  @appointment = Appointment.new
  @doctors = Doctor.all
  @patients = Patient.all
end

def edit
  @doctors = Doctor.all
  @patients = Patient.all
end
  <div class="field">
    <%= f.label :doctor_id %><br>
    <!-- Remember the select tags we used in the 
         association examples? Let's use that again, 
         twice this time! - adjusting the wording to 
         fit our purposes here. -->
    <%= f.select(:doctor_id, @doctors.collect 
      {|d| [ d.name, d.id ] }, {:include_blank => 
      'Please select a item'}) %>
  </div>
  <div class="field">
    <%= f.label :patient_id %><br>
    <%= f.select(:patient_id, @patients.collect 
      {|p| [ p.name, p.id ] }, {:include_blank => 
      'Please select a item'}) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>

appointments_controller.rb

appointments/_form.html.erb

making appointment is fun!

Displaying Appointments

Configuring the Appointments pages to show Doctor and Patients names is the easiest of the three.

appointments/index.html.erb

<table>
  <thead>
    <tr>
      <th>Doctor</th>
      <th>Patient</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @appointments.each do |appointment| %>
      <tr>
        <td>
          <%= appointment.doctor.name %>
        </td>
        <td><%= appointment.patient.name %></td>
        <td><%= link_to 'Show', appointment %></td>
        <td><%= link_to 'Edit', edit_appointment_path(appointment) %></td>
        <td><%= link_to 'Destroy', appointment, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

All the appointments!

It might be nice to add appointment date and time attribute(s) to this Resource. Just sayin'.

Doctors & their Patients: The code

As taken separately, the Doctor/Patients and Patient/Doctors relationships are essentially One-to-Many relationships. So we'll be needing to loop through an array.

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Patients</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @doctors.each do |doctor| %>
      <tr>
        <td><%= doctor.name %></td>
        <td>
          <% doctor.patients.each_with_index do |pat, index| %>
            <% if index < (doctor.patients.length - 1) %>
              <%= pat.name %>,
            <% else %>
              <%= " #{pat.name}" %>
            <% end %>
          <% end %>
        </td> 
        <td><%= link_to 'Show', doctor %></td>
        <td><%= link_to 'Edit', edit_doctor_path(doctor) %></td>
        <td><%= link_to 'Destroy', doctor, method: :delete, data: 
              { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @doctor.name %>
</p>

<p>
	<strong>Patients:</strong>
	<ul>
		<!-- We'll loop through the array of patients
		    and present them as an unordered list -->
		<% @doctor.patients.each do |patient| %>
			<li><%= patient.name %></li>
		<% end %>
	</ul>
</p>

<%= link_to 'Edit', edit_doctor_path(@doctor) %> |
<%= link_to 'Back', doctors_path %>

doctors/index.html.erb

doctors/show.html.erb

Doctors & their Patients: In the Browser

localhost:3000/doctors

localhost:3000/doctors/1

Patients & their Doctors: The code

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Doctors</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @patients.each do |patient| %>
      <tr>
        <td><%= patient.name %></td>
        <td>
          <% patient.doctors.each_with_index do |doc, index| %>
            <% if index < (patient.doctors.length - 1) %>
              <%= doc.name %>,
            <% else %>
              <%= " #{doc.name}" %>
            <% end %>
          <% end %>
        </td>
        <td><%= link_to 'Show', patient %></td>
        <td><%= link_to 'Edit', edit_patient_path(patient) %></td>
        <td><%= link_to 'Destroy', patient, method: :delete, data: 
              { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @patient.name %>
</p>

<p>
	<strong>Doctors:</strong>
	<ul>
		<!-- We'll loop through the array of doctors
		    and present them as an unordered list -->
		<% @patient.doctors.each do |doctor| %>
			<li><%= doctor.name %></li>
		<% end %>
	</ul>
</p>

<%= link_to 'Edit', edit_patient_path(@patient) %> |
<%= link_to 'Back', patients_path %>

patients/index.html.erb

patients/show.html.erb

Patients & their Doctors: in the browser

localhost:3000/patients

localhost:3000/patients/1

You did it!

You created a many-to-many association!

Rails Associations

By argroch

Rails Associations

Creating links between your Resource databases.

  • 1,395