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 (Ruby on Rails) 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 select tag, 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.
@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:
- 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?
- 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.
<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 %>)
< % end %>
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
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