Rails 3 Select List Items with Custom Attributes

This post is to assist with adding custom attributes (like the new html 5 data attributes) to items within a select control using Rails 3.

Scenario

I have a select control with Appointment types for a hairdresser. The items in the list are:

  • Men's Haircut
  • Women's Haircut
  • Kid's Haircut

Each of these items have a name (to be displayed) and an ID (to be saved to the database). I also however have a duration for each of these appointment types, and I want to do some client side (jquery) changes to my screen based on the appointment duration.

Options

Ordinarily with the items in the select control, I can only have one value stored, and I need this to be the ID. There are two ways to get around this:

  1. Store a combined key in the value like a pipe separated string "12|60" where the id is 12, and the duration is 60. You would then need to parse this back at your controller when dealing with this field.
  2. Add a custom attribute to the items in the select control (ie. data-duration). We have always been able to add custom attributes to HTML controls, however the HTML5 specification now makes this legit, as long as they are named with "data-" as the prefix.

Solution

I have opted for option 2, as I want to take advantage of rails automation of form controls when saving and loading the form.

Ordinarily I would have used a form.collection_select control for my select, but this does not allow for these extra attributes, so I will create my own custom helper that is based on "options_from_collection_for_select" and use it with a normal select tag.

First, I create the helper in my application_helper.rb file:

def options_from_collection_for_select_with_attributes(collection, value_method, text_method, attr_name, attr_field, selected = nil)
  options = collection.map do |element|
    [element.send(text_method), element.send(value_method), attr_name => element.send(attr_field)]
  end
  
  selected, disabled = extract_selected_and_disabled(selected)
  select_deselect = {}
  select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
  select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)

  options_for_select(options, select_deselect)
end

In the above helper, I have added two extra parameters:

  • attr_name This is the name of the attribute to add (eg. data-something)
  • attr_field This is the field in your model to get the value from (eg. duration)

Now in my view, I can use the normal select tag, and use my new helper within:

<%= f.select(:appointment_type_id, options_from_collection_for_select_with_attributes(Appointment_type.all, 'id', 'name', 'data-duration', 'duration',  @appointment.appointment_type_id)) %>

This will render my items like this:

<select id="appointment_appointment_type_id" name="appointment[appointment_type_id]">
  <option value="1" selected="selected" data-duration="30">Men's Haircut</option>
  <option value="2" data-duration="120">Women's Haircut</option>
  <option value="3" data-duration="30">Kid's Haircut</option>
</select>

By Joel Friedlaender