Accessing info from Database B via Database A.
And vice versa.
A.B.attribute
and/or
B.A.attribute
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!
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.)
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
That's really all it takes to do a one-to-one association!
The trick now is to make the calls between the databases...
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
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>
Now we have a nice dropdown selection tool:
We see the name, but it's the ID that's passed!
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 %>
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>
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
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.)
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>
Voila! A nice dropdown menu to choose our city's state!
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 %>
localhost:3000/cities
localhost:3000/cities/1
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 %>
localhost:3000/states/1
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>
localhost:3000/states
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
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
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
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.
First, let create several Doctors and Patients, so we have some data to work with.
localhost:3000/doctors
localhost:3000/patients
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
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>
It might be nice to add appointment date and time attribute(s) to this Resource. Just sayin'.
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
localhost:3000/doctors
localhost:3000/doctors/1
<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
localhost:3000/patients
localhost:3000/patients/1