has_many :through

The has_many => :through association joins two models through a third, allowing additional join information to be stored in the third model. In the example from Rails Guides: Association Basics, the physician and patient models are joined through an appointment model.

1. The Migration

Assuming the two resource models are already created, now create the join table:

rails generate model Appointment
db/migrate/timestamp_create_appointments.rb
class CreateAppointments < ActiveRecord::Migration
  def change
    create_table :appointments do |t|
      t.integer :physician_id
      t.integer :patient_id

      t.timestamps
    end
    add_index :appointments, :physician_id
    add_index :appointments, :patient_id
    add_index :appointments, [:physician_id, :patient_id], :unique => true
  end
end

The add_index lines make it faster to access information via the join model by instructing Rails to create an index to refer to. The last line includes :unique => true to ensure that there can only be one record joining each patient to each physician.

Migrate up:

bundle exec rake db:migrate

2. The Models

The following code will set up the association:

app/models/physician.rb
class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, :through => :appointments
end
app/models/appointment.rb
class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient
end
app/models/patient.rb
class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, :through => :appointments
end
:dependent => :destroy

Add :dependent => :destroy to the join model to destroy its records when a record from either of the two joined models is destroyed. Here, destroying a physician will remove all of its appointments. This action will NOT remove patients.

app/models/physician.rb
has_many :appointments, :dependent => :destroy
accepts_nested_attributes_for

Add accepts_nested_attributes_for to any model to allow another model to be edited via its views. Here, patients can be created from the physician view.

app/models/physician.rb
accepts_nested_attributes_for :patients

3. The View

The following line within the patient form will automatically create a select box listing all physicians. Selecting one will create a join record in the appointments table. (Uses Formtastic syntax.)

app/views/patient/edit.html.erb
<%= semantic_form_for @patient do |f| %>
    .
    .
    <%= f.input :physicians, :as => :select %>

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>