Building a Better Drop Down Selection List for Ruby on Rails

NOTE (updated 2008): this post is based on VERSION 1 of Rails from 2005!! For Ruby 1.8, Ruby on Rails 2.3.5 checkout a preamble to Building a Cascading Drop Down Selection List for Ruby on Rails with jQuery Ajax

In building the View part (using RHTML Templates) of a RoR () application we frequently want to present a controlled set or list of options for the user to select and way to do this is using the html , which is easy enough to do in RoR, either building it with embedded Ruby (ERb) and/or using the select() helper method, and with only a little more effort you can do so using information stored in a database table via your models, of course, using a find() method to get the values. The “Agile Web Development with Rails” AWDR book has some excellent information on doing this (Chapter 17 pages 357 – 359 ).

The collection_select helper adds in some extra sugar automatation dealing with the collection.

1
2
3
4
< %=
@items = Item.find(:all, :order => "name")
collection_select("stuff", "item_id", @items, :id, :name )
%>

However, I find the current ways of doing this lacking in two ways:

  1. The element present to the users to select are based on the values of only one field. What if you what to be more descriptive and present more information?
  2. The use of the selected=”selected” attribute is necessary when you are editing an existing record – easy, but also for new elements there is no way to present a default element, unless the first element is the default. But what if you what to set Manitoba as the default in a list of provinces (M being in the middle), or present a blank choice or a “Please Select a Widget Type” message?

With this goal in mind I set out to build my more user friendly generated Select.

1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="stuff_item_id" name="stuff[item_id]">
< % if @stuff.item_id == 0 or  @stuff.item_id == nil -%>
        <option value="" selected="selected" >Please Selected An Item</option>
  < % end -%>
  < % @items = Item.find(:all, :order => "name")
     @items.each do |item| %>
  <option value="<%= item.id %>"
   < % if @stuff.item_id == item.id -%> selected="selected"< % end -%>
   >
 < %= item.name %> (< %= item.description %>)
 </option>
 < % end %>
 </select>

which when used (when doing a “new”) I get something like this:

now for some explaining (assuming some knowledge of ruby and rails):
we are updating a stuff table/model with a field called “item_id”, there is also a table/model call item with fields “id”, “name”, and “description”

  • line 1: start the select tag. the name=”stuff[item_id]” is important to populate the “item_id” field in “stuff”
  • lines 2-4: checking the value in for the current record of stuff. When a new record is created it is zero, when it has failed validation it is “nil”. In either case this is where the default message (which might just be an emplty string), and value goes. You could also do a different default with validation fails.
  • line 5: gets all the values from the Item model, sorted by name, and stores it in @items.
  • line 6: start looping thru @items, one item at a time.
  • line 7: builds each option tag of the select and sets the value attribute to “item.id”, which is what will be actually stored in “stuff”.
  • line 8: sets the selected attribute if we are editing an existing “stuff” record.
  • line 9: close the front part of the option tag.
  • line 10: build what the user see, lin this case the name and in brackets the description
  • line 11:then close the option tag for this value of item.
  • line 12: close the looping for this item , which was started on line 6.
  • line 13 :close the select tag.

Hopefully this is clear enough to help others. I’m still exploring the documentation and the RoR api doc has a section on ActionView::Helpers::FormOptionsHelper, with some specialized select Helpers.

I did not address the “Manitoba as the default in a list of provinces (M being in the middle)” objection in the above code. To do so you have have to A) know or figure out that “Manitoba” had a value of 6, then B) , my guess at best – or least worst – practice, do a

1
if @stuff.item_id == 0 then @stuff.item_id = 6

to set this default value before the select.

Perhaps I should I will build a re-useable helper “collection_select_with_defaults”? …very very soon.

Amy Ho has some useful advice in A Little Form Help From Your Friends, all about Rail form helpers

I should also note that similar problems and solutions exist in PHP, JSP and ASP.

update: on the RoR wiki there is HowtoUseFormOptionHelpers

March 7, 2006 Update: , Sam Livingston-Gray not only linked here, but wraped it in a user *and* developer friendly helper function in the comments below

Update May 2010:

Building a Cascading Drop Down Selection List for Ruby on Rails with jQuery Ajax, for Rails 2.3.x.

14 Replies to “Building a Better Drop Down Selection List for Ruby on Rails”

  1. Your example places ‘Please Selected An Item’ as the displayed value. How would you display one of the @items only knowing its array position?

  2. Caution for dumbs like me! When you copy paste the code, the line numbers are included. So you have to delete them manually 🙂

    Very well documented beautiful code… Thank you very much.

  3. any reason why you did not use:

    options_from_collection_for_select(collection, value_method, text_method, selected_value = nil)

    ?

  4. Pingback: Powdered Non Sequitur Man » Blog Archive » Polite Dropdowns In Rails

  5. Found this yesterday via Google. I’ve been playing with Ruby and Rails for a week and a half, and wondered why there wasn’t any way to include a prompting value in a select tag. I’ve put together a tag helper that acts just like Rails’ “collection_select” method, but which takes an extra option called :first_row.

    The following code assumes I have a Client model object, and that I’ve declared a method on it called “name_first_last_active” that shows a display-friendly name. (This fits well in a mixin called “Person,” by the way.)

    I hope the following formatting comes through:

    def polite_collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
    # Get the HTML for everything in the collection as we ordinarily would
    options_html = options_from_collection_for_select(collection, value_method, text_method, options[:selected_value])

    # If we were given text for the first row, add another option tag with it
    first_row = options[:first_row]
    unless first_row.nil?
    first_row_html = content_tag(:option, first_row, :value => ”)
    options_html = “#{first_row_html}\n#{options_html}”
    end

    # Wrap the option tags in a select tag, giving it idiomatic id and name attributes
    html_options[:id] = “#{object.to_s}_#{method.to_s}”
    html_options[:name] = “#{object.to_s}[#{method.to_s}]”
    select_html = content_tag(:select, options_html, html_options)
    end

    By putting this in a helper (like application_helper.rb), you can call this as a one-liner, just like the regular “collection_select” method:

    “active DESC, first_name, last_name”) %>
    polite_collection_select(:foo, :client_id, @clients, :id, :name_first_last_active, :first_row => ‘Please select a client:’) %>

    Now it’s both user-friendly *and* developer-friendly. (= Hope this helps!

  6. like ocyrus, I think there is a simpler solution with the
    ptions_from_collection_for_select(collection, value_method, text_method, selected_value = nil)

    The new version would look something like:

    1
    2
    3

    This example assumes that you are passing in the @item parameter.
    The “.to_i” may be required to get a proper match from the string parameter passed in, to an integer id type.

  7. That’s wierd, for some reason the post stripped the code lines after the number. Anyway I’ll try again wihtout line numbers, it should look lie

  8. Any development on your collection_select_with_defaults? Or could you refer any other site for this (ie, to set the default in a collection_select for RoR)?
    Thanks.

  9. if we select one item in drop down then after updating the page when we see that there isn no item se4lected in drop down meny why tell me.
    the code is set

  10. how to have multiple selection option …
    “multiple” = “true” is showing combo box in which i can select more than one values …
    but not getting how to fetch those selected values in controller …
    Kindly help …

  11. Pingback: Building a Cascading Drop Down Selection List for Ruby on Rails with jQuery Ajax | False Positives

Leave a Reply