Friday, May 25, 2012

Rails: Updating Association Models w/ Form Checkboxes

Disclaimer: I am a Rails nublet, so please correct my speculation on behavior if needed!

I wanted to create a web form allowing a user to add/remove an object's association with another object via a third object. What?! In the world of databases, this is called an association table. Hopefully, I can make this more clear below.

The Model

Let's say we are modeling a clinic/pharmacy and we want patients to be able to request refills on their medications:

class Patient < ActiveRecord::Base
  has_many :medications, dependent: :destroy
  has_many :refill_medications, through: :refills

class Medication < ActiveRecord::Base
  belongs_to :patient

class Refill < ActiveRecord::Base
  belongs_to :patient
  belongs_to :medication

The Interface

With our model defined, we want an interface that will list each medication for the patient along with a checkbox. If the checkbox is checked, a refill will be created, linking the patient to his/her medication. This interface will look like the following:

Please check the medications you want refilled:

The html.haml for the form, using Rails, will look something like this:
= form_for @patient do |f|
  = hidden_field_tag "patient[refill_medication_ids][]"
  - @patient.medications.each do |medication|
    = check_box_tag "patient[refill_medication_ids][]",, @patient.refill_medications.include?(medication)

The Rails Magic

Taking advantage of Rails conventions, the attribute refill_medication_ids will be made available for use. Rails will automagically create this for you! This will be the ids for all medications with a refill (since it uses a has_many :through).

So that means on form submit, we are actually updating refill by modifying refill_medications. By passing a list of medication_ids, Rails knows to create a refill with that medication_id for the patient.

Why the Hidden Field?

We add a hidden field to the form because we want the form to pass an empty list when none of the checkboxes are selected. This will completely remove all refills for any medications, which is the intended behavior.

1 comment:

  1. you meant 'has_many :refill_medications, through: :medications" ?