Absolute Moron's Guide to Forms in Rails, Part 1 5

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.



What ffox theme is that?
Hey great article, thanks for sharing. Maybe look deeper at the generation of the form and what is being passed into it?
I just spent too much time checking out screenshots of every ff theme listed at http://learnfirefox.cybernetnews.com/2007/06/16/firefox-themes-a-e/ ... I agree with Nathan.
What Firefox theme is that??
@Joseph: We'll definitely do just that in the next article. I thought it would be too much to make this article any longer, and I'll try to get the next article up soon.
Firefox theme inquisitors: Actually that's not a new theme, I'm running Firefox 3 Beta 4 on Leopard with its new default theme.
Very cool article! Thank you for all these details about forms.
Eddie CA