Absolute Moron's Guide to Forms in Rails, Part 5 29
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.
Absolute Moron's Guide to Forms in Rails, Part 4

Radio Buttons in Rails
Today we continue our tour of forms in Rails. Let’s continue by learning how to add radio buttons onto our form.
In Part 1 our form contained a textbox so that we could create a new flight given a flight number, and in Part 2 we added a checkbox for meal service. Let’s add a few radio buttons so that we can associate the flight with the kind of airplane that should be assigned to the flight.
Adding another column to the Flights table
Airlines often refer to the model of airplane as the “equipment,” so I’m going to use that term instead of the potentially confusing term “model”. Create another migration file, but this time we’ll use some command-line options to make our job easier:
script/generate migration AddEquipmentToFlight equipment:string rake db:migrate
or for those on Windows
ruby script\generate migration AddEquipmentToFlight equipment:string rake db:migrate
Presto, we’ve added a string column named equipment to the Flights table.
Adding Radio Buttons
Now that we have our new column, let’s add some radio buttons. Open up the new.html.erb template again, and use the form builder object to add three radio buttons:
<p>
<p>
<%= f.radio_button :equipment, '747' %><b>Boeing 777 (Jet, Nice)</b>
</p>
<p>
<%= f.radio_button :equipment, 'MD80' %><b>MD-80 (Jet, Old)</b>
</p>
<p>
<%= f.radio_button :equipment, 'Cessna' %><b>Cessna (Propeller)</b>
</p>
Navigate to the form in your browser:

As always, look at the actual HTML that got generated. It’s important to understand the HTML that your embedded Ruby generates. Here’s the HTML for the first radio button:
<input id="flight_equipment_747" name="flight[equipment]" type="radio" value="747" /><b>Boeing 777 (Jet, Nice)</b>
As you know by now, the name attribute follows the conventions necessary to make good things happen in our params hash. Notice that all of our radio buttons have the same name attribute, but that the browser will only submit the name/value pair for the button that the user selects. Radio buttons with the same name are considered part of the same radio button group, so that the user can only select one choice per group.
Go ahead and choose an airplane, and then click the Create button. Open up script/console to convince yourself that the equipment value was saved:
>> f = Flight.find_by_number '567'
=> #<Flight id: 3, number: "567", created_at: "2008-04-06 09:55:14", updated_at: "2008-04-06 09:55:14", meal: false, equipment: "Cessna">
>> f.equipment
=> "Cessna"
By now, using form_for and the form builder methods should be feeling more natural. Next time, we’ll be adding listboxes and combo boxes (drop-down lists). In the meantime, if you have comments or questions on what we’ve done so far, leave us a comment below.
Absolute Moron's Guide to Forms in Rails, Part 2 6

Last time, we created a Flight resource and took a peek at the generated scaffolding code. In particular, we opened up the new.html.erb template. Today we’re going to take a closer look at the code in that template to understand how it really works.
Recall one of the core concepts we established last time: requests from the browser travel over a bridge and into your controller code. That bridge is the params hash, generated for you by Rails itself, and is your code’s window to the outside world.
With that concept in mind, and without further ado…
The “New” form submits to the “Create” action
Rails views that end with the suffix .html.erb are designed to generate html, which in turn will be sent back to the browser. Let’s look at the new.html.erb file again:
<% form_for(@flight) do |f| %>
<p>
<b>Number</b><br />
<%= f.text_field :number %>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
The .erb extension indicates “embedded Ruby”, and indeed we can see Ruby code that’s been embedded into the surrounding HTML landscape. The Rails pipeline executes the Ruby code, and then sends the entire resulting HTML back to the browser. To see what got generated, use the View Source feature of your browser and locate the <form>:
<form action="/flights" class="new_flight" id="new_flight" method="post">
<div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="025e8a2b92e325ea0a34ecf040693c0f74262a41" /></div>
<p>
<b>Number</b><br />
<input id="flight_number" name="flight[number]" size="30" type="text" />
</p>
<p>
<input id="flight_submit" name="commit" type="submit" value="Create" />
</p>
</form>
Let’s zoom in on the form element itself:
<form action="/flights" class="new_flight" id="new_flight" method="post">
That’s the HTML tag for creating a form. In HTML, the action parameter is required and tells the browser what to do when the user clicks the submit button.
- We want the browser to call our
createaction, so we use the url/flights - Similarly,
methodtells the browser which HTTP verb to use. In this case, we’re using POST
Those of you who paid attention during our REST series will recall that for a resource named Flight, the url /flights with a method of POST will trigger the create action in a controller named FlightsController in our Rails application.
form_for and More
Now, how did we generate this form tag with these attributes? We did it with the form_for method in Rails:
form_for(@flight) do |f|
form_for is only useful for creating a form to act upon an ActiveRecord-derived model, but that’s the 80% case when developing Rails applications. Suffice to say, you’ll be using form_for a lot.
form_for knew how to generate the <form> tag and all of its attributes by examining the flight variable. Recall how this variable was created by the controller’s new action:
@flight = Flight.new
Since it’s a new Flight instance, and has not yet been saved to the database, form_for knows that we want to make a form for creating a new flight in the database, not for updating an existing flight record. Therefore, our HTML form will want call the create action; and as a result, the url must be /flights and the method must be set to POST.
(Yes, if you’re not following RESTful controller conventions, you can override the url and method yourself; but don’t expect me to help you with that: you can go look it up yourself, you non-conformist, you.)
Back in Part 1, we explained that between the browser and your controller code is a bridge. The bridge is built mostly of your routes.rb file, which enables Rails to transform incoming HTTP requests into a single Ruby hash called params.
Let’s zoom in on that textbox in our form. First, look at the Ruby code in our template:
<%= f.text_field :number %>
which became this HTML
<input id="flight_number" name="flight[number]" size="30" type="text" />
How did it do that and why?
- We are using the block variable,
f. fis given to us byform_forand knows how to generate form elements for us.fis called a form builder.- We are calling the
text_fieldmethod, which specifically knows how to generate aninputtag withtype="text". - The first parameter,
:number, helps it generate a theidandnameattributes of theinputtag.
The id attribute is helpful for CSS and DOM access, but has no bearing on what happens when the form is submitted. The name attribute, on the other hand, is very important. Rails will automatically populate the params hash with data coming in from the form. It uses the name attribute of the input element as the key in our hash.
If this doesn’t make sense right away, let’s take a step back for a moment. Suppose our HTML looked like this instead:
<input id ="my_id" name="sport" type="text">
This creates a text field named sport. Suppose further that we typed the value “hockey” into this text field and submitted the form. What would happen? Rails would add a key of “sport” with the value “hockey” into the params hash, and we could easily discover what was typed into the text field like this:
def create
favorite_sport = params[:sport]
# favorite_sport is now 'hockey'
...
end
Now for the cool part. You can isolate all of your form’s data into your very own hash inside the params hash by following a simple convention. In the name attribute of your input tag, use square brackets. The part before the brackets will be name of your hash, and the part inside will be the key into your hash. That’s what f.text_field :number did for us:
<input id="flight_number" name="flight[number]" size="30" type="text" />
Here, the name is flight[number]. When Rails creates the parameter values for us, it will create a nested hash called flight, and inside that hash, it will create a key called number, and its associated value will be whatever was typed into the form as the flight number. The big payoff for all of this? params[:flight] will give us a hash of all of the key/value pairs submitted by our form.
We can now understand why this code that was generated in our create action:
@flight = Flight.new(params[:flight])
This creates a new Flight object, instantly initialized with all of the data coming from our form.
The Catch
In order for this magic to work, you have to be careful how you use the form builder object. The first parameter must be the name of an attribute on your model (that is, a column in your table). Rails not only uses that attribute to pre-fill the form for you based on the model object’s initial state, but the Flight.new(params[:flight]) code is expecting attribute/value pairs that correspond only to your model’s attributes.
What About Other Controls?
We’ve traced the lifecycle of a textbox from Ruby template, to HTML, to params hash, to controller code. In the next article we’ll learn how to use the same form builder object to easily bind checkboxes, radio buttons, and list boxes to our model as well.
Absolute Moron's Guide to Forms in Rails, Part 1 9

Today we’re starting a new series that we hope will help those who are new to Rails, and especially those who might be new to web development (helloooo, all you graphic designers out there!). We’re going to begin today with a very small form with just one text field, to demonstrate the important connection between a form and a controller. Subsequent articles will demonstrate how to use HTML controls like combo boxes, list boxes, and radio buttons.
Forms in Rails
We’re presuming you know Ruby, have done a little work with Rails, and have read our introduction to REST or are already familiar with how to write RESTful controllers.
We’re also presuming you’re using Rails 2.0.2 or higher. Do a rails -v to make sure, and a gem install rails if you need to upgrade.
Let’s get started. For this series, we’ll be using an example based on a brand new Rails application, and you can code along with it if you want.
Start a new Rails application:
rails forms101 cd forms101
If you’re on Mac OS X 10.5, or Windows with SQLite3 installed, you’re ready to go. Any other case, you’ll need to create your development and test databases manually, or do this:
rake db:create:all
Em Vee Cee
Say it with me now: “M-V-C.” Everything you learn in Rails must always be put into the context of its architecture, and learning how to create web forms in Rails is no different.
To demonstrate, we’ll need a model to play with. Heck, let’s just generate a controller and some views while we’re at it. We’ll continue our theme from the REST series and use airline flights as our example.
Generate a full MVC scaffold for a flight, with a flight number:
script/generate scaffold flight number:string
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/flights
exists app/views/layouts/
exists test/functional/
exists test/unit/
create app/views/flights/index.html.erb
create app/views/flights/show.html.erb
create app/views/flights/new.html.erb
create app/views/flights/edit.html.erb
create app/views/layouts/flights.html.erb
identical public/stylesheets/scaffold.css
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/flight.rb
create test/unit/flight_test.rb
create test/fixtures/flights.yml
exists db/migrate
create db/migrate/003_create_flights.rb
create app/controllers/flights_controller.rb
create test/functional/flights_controller_test.rb
create app/helpers/flights_helper.rb
route map.resources :flights
On Windows, your command line would be:
ruby script\generate scaffold flight number:string
Next, update the database:
rake db:migrate
And now we can instantly play with our new Flight model:
script/console >> Flight.count => 0 >> Flight.create :number => "123" => #<Flight id: 1, number: "123", created_at: "2008-03-20 16:39:33", updated_at: "2008-03-20 16:39:33"> >> Flight.count => 1 >> Flight.find :first => #<Flight id: 1, number: "123", created_at: "2008-03-20 16:39:33", updated_at: "2008-03-20 16:39:33"> >>
We’re off the ground, so to speak.
Params Are Your Friends
We now have one, glorious flight record in our database. Let’s see it in a browser. Start up a local web server on port 3000:
script/server
(or ruby script\server on Windows).
and now go to localhost:3000/flights/1 in your browser:

Not very exciting, since our model has only one attribute.
Now for the important part: look at your development.log file. You should have something like this:
Processing FlightsController#show (for 127.0.0.1 at 2008-03-20 16:58:14) [GET]
Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--3336be1e6bd3c65be2845c8980bf23a200d5b678
Parameters: {"action"=>"show", "id"=>"1", "controller"=>"flights"}
Flight Load (0.000268) SELECT * FROM flights WHERE (flights."id" = 1)
Rendering template within layouts/flights
Rendering flights/show
Completed in 0.05683 (17 reqs/sec) | Rendering: 0.00181 (3%) | DB: 0.00027 (0%) | 200 OK [http://localhost/flights/1]
Smack in the middle is the line we need to look at:
Parameters: {"action"=>"show", "id"=>"1", "controller"=>"flights"}
This part of the log is important, because it shows the how the routes.rb file worked to map the incoming request header into parameters your application can work with. In the case of a GET request, this really just means looking at the url and splitting into pieces. For POST, PUT, and DELETE, it includes slurping up any encoded data in the request body as well, which we’ll see later on.
Now that the params have been determined, Rails can call an action in one of your controllers. Here’s our code for the show action:
def show
@flight = Flight.find(params[:id])
...
end
We use the params hash to access the parameters. This hash your controller’s window to the outside world, and how it finds out what it’s been asked to do. Rails has already done some heavy lifting for you, by creating the right controller as specified by params[:controller] and called the method specified by params[:action].
Note that although the log file showed the hash keys as strings ("id") we can access them using the more efficient symbol syntax once we’re inside our controller action (params[:id]).
Where’s My GOO-EEE
That was fun, but we can’t have our employees at SoftiesAir firing up script/console whenever they need to add a flight to the database. Forms allow us to create models in a browser.
Take a look at this generated code for the new action in app/views/flights/new.html.erb:
<h1>New flight</h1>
<%= error_messages_for :flight %>
<% form_for(@flight) do |f| %>
<p>
<b>Number</b><br />
<%= f.text_field :number %>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
<%= link_to 'Back', flights_path %>
In the browser it looks something like this:

Not every exciting. Just a text field and a submit button. We can use it to create a new flight. Enter a flight number like 12, click Submit, and the point your browser to /flights:

Congratulations, we created a new flight in our database! But what really happened make that work?
Over Troubled Water
Remember, in order for our controllers to “see” what happened in the browser, it has to get its information from the params hash. This hash is the bridge between our HTML form and your code.
When we clicked “Submit” on the New Flight form, we sent a bunch of data scurrying over that bridge as it made its way into the create action of the Flights controller. Look at the log again, and you’ll see something like this:
Processing FlightsController#create (for 127.0.0.1 at 2008-03-21 09:32:08) [POST]
Session ID: BAh7BzoMY3NyZl9pZCIlZDk1ODM5ZmY0MGVmZGI0NDgyNDU4YTM5MDIwY2Ri%0ANDkiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--6c4d1d7f05c3076adfc4815439a12abd895715a1
Parameters: {"commit"=>"Create", "authenticity_token"=>"dda0b1eb89899c0d8bdeee1e4e0adddda5ad7cb4", "action"=>"create", "controller"=>"flights", "flight"=>{"number"=>"12"}}
Flight Create (0.000505) INSERT INTO flights ("updated_at", "number", "created_at") VALUES('2008-03-21 09:32:08', '12', '2008-03-21 09:32:08')
Redirected to http://localhost:3000/flights/2
Completed in 0.00950 (105 reqs/sec) | DB: 0.00050 (5%) | 302 Found [http://localhost/flights]
Once again, let’s zoom in on the parameter data that came over the bridge:
Parameters: {"commit"=>"Create", "authenticity_token"=>"dda0b1eb89899c0d8bdeee1e4e0adddda5ad7cb4", "action"=>"create", "controller"=>"flights", "flight"=>{"number"=>"12"}}
We still have the action and controller values as before. But now we also have a flight hash key, and it is itself a Ruby hash:
"flight" => { "number" => 12 }
If we look at the generated code for the create action, we see this:
def create
@flight = Flight.new(params[:flight])
...
which is the same as saying:
@flight = Flight.new("number" => 12)
which is recognizable now as the normal way we create a Flight object initialized with the value 12 for the number attribute.
Connecting The Last Dot
We’ve seen how HTTP request data goes through our routes, and becomes a hash of parameters that in turn get handed to our controller. In order for the Submit button on our form to result in a new Flight object in our database, we have to make sure that the HTTP request data is sent in the manner that Rails expects and can understand. That takes us right back to where we started – the new.html.erb code – which we will explore in depth next time.
Comments, questions, complaints? Drop us a comment.


