Absolute Moron's Guide to Forms in Rails, Part 5 42
List Boxes in Rails
Today we will tackle the more thornier controls: list boxes and combo boxes. Let’s add the ability to select the origin and destination airports for our flight from a list of available airports.
Airports
We need a small list of airports to choose from, so let’s create a lookup table.
script/generate scaffold Airport code:string rake db:migrate
Use the generated scaffold UI at /airports to add a few airports. Here’s what I chose:

We also need add two columns to our flight model:
script/generate migration AddAirportsToFlight origin_id:integer destination_id:integer rake db:migrate
Notice that we’re storing integers, not strings this time. This is so we can hold foreign keys to the appropriate rows in the Airports table. We also need to open up the flight.rb model file and specify the relationship between flights and airports.
class Flight < ActiveRecord::Base
belongs_to :origin, :class_name => "Airport"
belongs_to :destination, :class_name => "Airport"
end
If you’re not very familiar with ActiveRecord yet, the code here uses a slightly more advanced syntax of the belongs_to method since the lookup table class name is not related to the attribute names we want in our Flight class. (If Rails knew that “origin_id” and “destination_id” are intended to be foreign keys to the Airports table, that would be magic indeed).
Similarly, let’s hook things up on the Airport side of the equation:
class Airport < ActiveRecord::Base
has_many :departures, :class_name => "Flight", :foreign_key => "origin_id"
has_many :arrivals, :class_name => "Flight", :foreign_key => "destination_id"
end
Before we use any GUI sugar, open up your console and hook up a flight to two airports (I’ve omitted a bunch of the console output for clarity):
>> f = Flight.find(1)
=> #<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-05 20:05:24", meal: false, equipment: nil, origin_id: nil, destination_id: nil>
>> ord = Airport.find_by_code 'ORD'
>> pdx = Airport.find_by_code 'PDX'
>> f.origin = ord
>> f.destination = pdx
>> f.save!
=> true
And now, behold the beauty and power of ActiveRecord associations that give us a natural-language vocabulary for our models.
>> ord.departures
=> [#<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-06 10:22:23", meal: false, equipment: nil, origin_id: 1, destination_id: 2>]
>> pdx.arrivals
=> [#<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-06 10:22:23", meal: false, equipment: nil, origin_id: 1, destination_id: 2>]
Awesome!
Now let’s see how we can make those associations in our form instead of through the console.
Selecting Airports
In HTML, listboxes and combobox (drop-down list) controls are both known as selection lists, and are implemented by the <select> tag. Nested inside the <select> tag are one or more option tags. Each option describes one list item.
The form_for builder gives us two waysto construct selection lists without having to manually construct each <option> tag individually. One is the select helper, which provides full control over how the <select> and <option> tags are generated. Another is collection_select, which provides a simpler syntax if:
- You can provide the list of items as a Ruby collection
- Each item in the collection has a method that can be called to provide a displayable string for that item
- Each item also has a method that can be called to supply the value that should be sent to the application when the item is selected by the user.
These three conditions are the 80% case when you’re constructing the list from an ActiveRecord table. Some code will make it clearer. Open up new.html.erb again and add this code into the form:
<% airports = Airport.find(:all, :order => :code) %>
<p>Depart from:
<%= f.collection_select :origin_id, airports, :id, :code %>
</p>
<p>Arrive at:
<%= f.collection_select :destination_id, airports, :id, :code %>
</p>
We start by capturing all of the airports into the airports variable. Note that we use the <% %> syntax, without the equal sign, whenever we don’t want the result to be sent back to the browser.
We then use the collection_select helper to construct our combo box. Here are the parameters we had to pass:
- First is the attribute of our model we’re trying to assign to (origin_id or destination_id)
- Second is the Ruby collection. We will get one
<option>tag – one item in the list – for each object in the collection. - Third is the method on the collection items that should be called on the selected item when it’s time to transmit the selection to the application.
- Last is the method on the collection items that should be called to generate that item’s displayed string in the list.
Navigate to the form in your browser:

Finally, let’s make sure we understand how this ends up in our new flight model. Select some values and click Create.
Back To That Bridge Again
In my case, I created flight #444 from Chicago to Portland on a Boeing 777. Take a look at your log file (log/development.log) and find where the create action was called. Mine looks like this:
Processing FlightsController#create (for 127.0.0.1 at 2008-04-06 14:31:39) [POST]
Session ID: BAh7BzoMY3NyZl9pZCIlZmRhM2FmODI0ZjA1MjYwMzgxMGJiMjcyYjBiOTNi%0AMTUiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--273a533fb4cee21ea44cb1ccdc6d70db908d7525
Parameters: {"commit"=>"Create", "authenticity_token"=>"e9e1c0b0d744f33db348cd70a69cf70d6126c66b", "action"=>"create", "controller"=>"flights", "flight"=>{"number"=>"444", "origin_id"=>"1", "meal"=>"0", "destination_id"=>"2", "equipment"=>"747"}}
Flight Create (0.000350) INSERT INTO flights ("updated_at", "number", "origin_id", "meal", "destination_id", "equipment", "created_at") VALUES('2008-04-06 14:31:39', '444', 1, 'f', 2, '747', '2008-04-06 14:31:39')
Redirected to http://localhost:3000/flights/4
Completed in 0.00958 (104 reqs/sec) | DB: 0.00035 (3%) | 302 Found [http://localhost/flights]
Zooming in on just the parameters and reformatting for clarity:
Parameters: { "commit"=>"Create",
"authenticity_token"=>"e9e1c0b0d744f33db348cd70a69cf70d6126c66b",
"action"=>"create",
"controller"=>"flights",
"flight"=> { "number"=>"444",
"origin_id"=>"1",
"meal"=>"0",
"destination_id"=>"2",
"equipment"=>"747"}
}
See how all of our form data is in the tidy flight hash? We can easily access all the form values with params[:flight]. The important thing to notice are the values that came across for the selected airports.
Here’s another way to confirm to ourselves that it worked. Open up the Rails console again and find the flight:
>> f = Flight.find_by_number '444'
=> #<Flight id: 4, number: "444", created_at: "2008-04-06 14:31:39", updated_at: "2008-04-06 14:31:39", meal: false, equipment: "747", origin_id: 1, destination_id: 2>
>> f.origin.code
=> "ORD"
And for fun, check out the arrivals for the Portland airport:
>> Airport.find_by_code("PDX").arrivals
=> [#<Flight id: 1, number: "123", created_at: "2008-04-05 20:05:24", updated_at: "2008-04-06 10:22:23", meal: false, equipment: nil, origin_id: 1, destination_id: 2>, #<Flight id: 4, number: "444", created_at: "2008-04-06 14:31:39", updated_at: "2008-04-06 14:31:39", meal: false, equipment: "747", origin_id: 1, destination_id: 2>]
List Box instead of Drop-down Box
If you’d prefer to show a listbox instead of a combobox, we need to tell the browser to show more than one item at a time. To do that we need to pass two more parameters to the collection_select method:
- An empty hash. See the Rails API docs for more info here – this hash can contain optional elements like whether to include a blank item at the top of the list, etc.
- A hash of attributes that should be glued into the HTML
<select>tag. We need to insert a size attribute with the value 3. Anything larger than 1 will end up looking like a listbox instead of a combobox.
Adding these options and playing with our <p> tags a bit means we have code that now looks like this:
<p>Depart from:</p>
<%= f.collection_select :origin_id, airports, :id, :code, {}, {:size => 3} %>
<p>Arrive at:</p>
<%= f.collection_select :destination_id, airports, :id, :code, {}, {:size => 3} %>
</p>
and the form now looks like this:

You Are Now Free To Create Your Own Forms
We’ve gotten a few questions regarding the best way to handle several different models on the same form, multiples of the same model on the same form, and how to use advanced options of how to use form_for when your controller is a namespace (if your controller is within an “admin” namespace, for example). We’ll try to address those issues in subsequent postings in the very near future.
But for now, that’s it for our whirlwind tour of forms in Rails. Remember:
- Ruby templates generate HTML. So learn HTML.
- The
paramshas is your bridge between your user and your application. Learn to use the log files well. - Follow Rails conventions to make life easy.
Questions? Drop us a comment.




Great series, thanks!
(First, I want to apologize for any bad English in this comment.)
I just have a question regarding this line: <% airports = Airport.find(:all, :order => :code) %>
As a Rails beginner I was wondering whether it would be more Rails-like to put this line into the flights_controller like this:
(First, I want to apologize for any bad English in this comment.)
I just have a question regarding this line: <% airports = Airport.find(:all, :order => :code) %>
As a Rails beginner I was wondering whether it would be more "Rails-like" to put this line into the flights_controller like this: @airports = Airport.find(:all, :order => :code) %>
Thanks for your great blog! It really helped me to get over a lot of things!
Benny,
You are absolutely correct. I'd recommend moving that to the controller code.
Thanks for asking!
Jeff thanks a lot for this Absolute Moron's guide series! It's great to read through and get some familiarity with RoR! Great work! Cheers!
Jeff your series is great! I have learnt a lot today, thankyou very much!
Hi Jeff. Love the blog and this series in particular. I was wondering if you could do one on building forms without using a model.
Jeff,
Thanks so much for your help. This is a great blog, and has explained some concepts that my aging mind found tough to pick up in the past.
Sorry about your 'Hawks. I share your pain, as I am Blues fan from way back. I long for the days of the Blues-Blackhawks rivalry. Roenick, Chelios, Savard, Graham, et. al. But especially the Al Secord days.
@austinwebdeveloper: I will try to do that soon. Thanks for the suggestion!
@Adrian: Savard-Larmer-Secord was the best line the Hawks had since the 1960's and haven't had one since. Loved the Blues back in the day, especially Bernie Federko and Brian Sutter.
Hi,
Thanks for this blog…...... I was searching for something like this….. But I want to select an item the collection_select then pass it to another action(delete/edit) .... but I cannot pass the value to another action without submitting the form….(I use start_from_tag) .... Could you please suggest something…. I can provide with more details if needed. Thanks, Anp
I’m having trouble with the Airport.find_by_code ’ _ ’ snippet. I’m getting a ‘method_missing’ in the /usr/local/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:1483. Also, can’t seem to find any documentation in the api about a “find_by_code” method. Please help a noob!
sc
Anp: Sounds like you're trying to use an Ajax observer. See @observe_fieldin the Rails, it should do exactly what you’re looking for.Seth: Make sure you have a column named "code". @find_by_codeis an example of a “dynamic finder”. In Rails, you can dofind_by_columnwherecolumnis any column in your table. If that doesn’t help, post something on our forum and we can take it from there.As is proven in the ontological manuals, the never-ending regress in the series of empirical conditions, even as this relates to reason, would be falsified.
As is proven in the ontological manuals, what we have alone been able to show is that, so far as regards the discipline of natural reason and our ideas, the noumena are just as necessary as the empirical objects in space and time, and the Categories can be treated like the discipline of natural reason.
Awesome set of posts! I like how in depth you get with explaining things – so many tutorials gloss things over with statements to the effect of “and then, by the magic of rails…” but you really explain what’s going on which is great. You’ve helped me clarify a lot. : )
I must also say, I would love to see some stuff on use of fields_, multiple models in one form and dynamic forms. I understand you’ve already gotten requests for this sort of thing, but I suppose a little bit of reiteration doesn’t hurt.
One more thing – when at http://www.softiesonrails.com/tags/forms the third part to the series seems to be missing. Is this some error? Also it would be nice if on each individual post you had a link to the next and previous posts in the series.
Thanks again!
The tutorial is great, but leaves a bit out: Once you add all this data to the new method, you will want to see it in the index method. I have worked through most of the issues, but one has me stopped – after adding the origin and destination, I would like to show these values in the index method of flight, but for the life of me I can’t get the right way of pulling the airport.code against the flight.origin_id and flight.destination_id to work right in the index method. If possible I would love to see how this is supposed to be done (best practice). Any help would be appreciated. Also, it is worth noting that the format for display of checkboxes and buttons is different for display than it is for forms – a factor that costs be a bit of time working through. Thanks in advance for any assistance.
Chris: I’ll check out the problem with the tags. Thanks for the heads up.
Cecil: Thanks – and I’ve replied to your post in the forums here
Is there a way to get a multi-select list box?
Thanks.
Great series of tutorials! Perfect introduction for a Rails beginner as myself. Today i went through all the REST tutorials and the Forms in Rails tutorials. All went smoothly until part 5, top of the page. I’ve typed in: script/generate scaffold Airport code:string rake db:migrate And when i go to http://localhost:3000/airports all looks well. Only problem is that when i create a new airport nothing happens, no airport is added. I get this again: “Listing airports Code New airport”
This is in my development.log though :Parameters: {“airport”=>{“code”=>”jfk”}, “commit”=>”Create”
Am i missing something here or have i just been staring at this screen too long? :) Thanks!
Mark > Try to restart your web server. If you are using webrick, do Ctrl+C and then run script/server again.
Hope this helps.
To answer my own questions:
Multi-select listbox: {:multiple => ‘multiple’} %>
Hello Garg, thanks, works now:)
site sea steven look yes sea canada all girl tree yes global german
microsoft red head see stone sea greed cube global car girl busy england
busy right sea canada microsoft look red speed all stone england usa busy
key elephant free english boy tree wood
minor bag speed red we jhon yes juicy
Jeff, This is a great series. You should publish a non-model based form series as well. I am an experienced Rails developer and I still got a lot out of this series. You have a talent for explaining things in the simplest possible terms. I think that if you write a book that takes a “views” centric approach to Rails and shows how to gradually build up to nice looking Rails forms, I would be one of the first to buy it. A lot of Rails books focus on the “model” or ActiveRecord aspect of Rails which is no doubt important, but has been explained over and over again. Whereas, there is a shortage of books focusing on views and show how to build professional looking web-sites using Rails. Keep up the good work. Bharat
Hi Bharat,
Thanks for the kind words! The lack of decent documentation around forms in Rails inspired this series. I’m hoping that the new documentation being written at http://guides.rails.info will begin to address the need for clear explanations of all the UI-related features in Rails.
In the meantime, your suggestion for a series addressing non-model forms is an interesting one. Can you give me a couple of examples of where you’ve used non-model forms in your Rails applications?
Thanks Jeff
Hey Jeff!
You can kill the ‘spider spam’ in comments by using Captcha. It generates an image or distorted letters and numbers which the user then enters. Spiders and bots can’t read the images. If the user gets it right the comment gets posted. If not the comment page goes back one page. This frustrates the spiders & bots, giving them a dose of their own medicine. After a few wasted tries they give up.
Non-model form use case:
I’m working on a precinct lookup tool. You input your address, and it returns your precinct and polling location. I don’t ever want to store the user’s address, only use it to find what precinct they live in. It mostly works, values are passed through params, but I can’t find a simple way to make the values remain if there was an error and I have to send the form back for correction.
My bad, I was using FormHelper instead of FormTagHelper
- <td><%= label "form_data", "street_num", "Street Number"%></td> - <td><%= text_field "form_data", "street_num", :size => "10"%></td> + <td><%= label_tag "street_num", "Street Number"%></td> + <td><%= text_field_tag "street_num", @params[:street_num], :size => "10"%></td> </tr><tr> - <td><%= label "form_data", "street", "Street Name"%></td> - <td><%= text_field "form_data", "street" %></td> + <td><%= label_tag "street", "Street Name"%></td> + <td><%= text_field_tag "street", @params[:street] %></td> </tr><tr> - <td><%= label "form_data", "city", "City"%></td> - <td><%= text_field "form_data", "city" %></td> + <td><%= label_tag "city", "City"%></td> + <td><%= text_field_tag "city", @params[:city] %></td> </tr><tr> - <td><%= label "form_data", "state", "State"%></td> - <td><%= text_field "form_data", "state", :size => "2" %></td> + <td><%= label_tag "state", "State"%></td> + <td><%= text_field_tag "state", @params[:state], :size => "2" %></td>Great articles. I now have a question. On the flights ‘show’ page, how would I show the destination code? Currently I can just show the destination id. I assume I would have to do some type of join? I’m looking for a way to: show destination_code where Destination.destination_id = Flight.destination.id.
Thanks again for your tutorial. -Aaron
Great articles. I now have a question. On the flights ‘show’ page, how would I show the destination code? Currently I can just show the destination id. I assume I would have to do some type of join? I’m looking for a way to: show destination_code where Destination.destination_id = Flight.destination.id.
Thanks again for your tutorial. -Aaron
Wow nice database relationship! TNX so BIG
thanks very much! The explaination on drop down lists and selectbox were good! if i had only found this article earlier. This should be added to the rails API (which is lacking a lot).
Good job!
Is it just me, or are the forums down? I’m looking for your answer to Cecil regarding displaying the results of the join on the Index page.
Thank you for this great tutorial. Slowly all pieces are falling together! :-) Keep up the great work!
How to get the multi-select work? I used multiple=> true and now can select multiple values..but what is the code in the controller so that multiple objects are written to the database?
This is easily the best and most humorous tutorial I’ve done for any coding. You made this the easiest and funnest thing in the world. Thanks so much. I certainly hope you continue to put out great work.
This is easily the best and most humorous tutorial I’ve done for any coding. You made this the easiest and funnest thing in the world. Thanks so much. I certainly hope you continue to put out great work.
This is pretty good, but what I would like to find out is how to create a combo box type control that has an up arrow and down arrow. I would like to have it so the box displays integers and when one clicks the up arrow the integer adds 1 unit and when the down arrow is clicked it minuses 1 unit. Furthermore, I would like the lowest possible value to be 0 (also the default value for that matter) and have no cap on the highest possible value…can this be done?