Absolute Moron's Guide to Forms in Rails, Part 3 11

Posted by jeff Sunday, April 06, 2008 14:03:00 GMT

Pretzels anyone?

Today we continue our tour of forms in Rails. Parts one and two established these key ideas:

  • Your html.erb templates generate raw HTML form code for the browser.
  • Forms that target a model should use form_for to make life easy.
  • Your routes.rb is used by Rails to transform HTTP requests from the browser into a Ruby hash that’s always called params inside your controller.
  • The params hash is your bridge between the browser and your controller.
  • If you follow simple conventions for the name attribute of your HTML controls, Rails will automatically populate the params hash in ways that make it easy to spoon-feed your models with data.

Let’s continue by learning how to add a check box onto our form.

In Part 1 our form contained a textbox so that we could create a new flight given a flight number. Let’s also indicate if there will be meal service on the flight.

Adding a new column to the Flights table

First we need to add a new attribute to our Flight model. We have two choices for altering the table. We can modify our existing migration file and run the migrations from the beginning (rake db:migrate:reset), but that’s not going to help us if we’ve already deployed this app to production. Let’s say that the app is already deployed, and so we will go ahead and create a new migration file:

script/generate migration AddMealToFlight

or for those on Windows

ruby script\generate migration AddMealToFlight

Open the new migration file and you should have something like this:

class AddMealToFlight < ActiveRecord::Migration
  def self.up
  end

  def self.down
  end
end

Add some code inside the up and down methods to add a boolean column to our table:

def self.up
  add_column :flights, :meal, :boolean, :default => false
end

def self.down
  remove_column :flights, :meal
end

We’ve added a boolean column named meal which will, of course, default to false, because nowadays it seems only flights longer than two days come with free meals.

Finally, update the database:

rake db:migrate

Rails does a nifty thing with boolean columns: it supports the Ruby question mark convention for object attributes that return a boolean Ruby value. Open up your Rails console to see what I mean:

script/console
Loading development environment (Rails 2.0.2)
>> 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>
>> f.meal?
=> false

Me Want GUI

Now that we have our new column, let’s add a checkbox to our form so that we can set the meal value when we create the flight.

Open up the new.html.erb template again, and use the form builder object to add a checkbox:

<p>
  <%= f.check_box :meal %><b>Meal Service</b>
</p>

Navigate to the form in your browser:

Let’s take a quick look at the actual HTML that got generated. You’ll see some new HTML inside our form:

<p>
  <input id="flight_meal" name="flight[meal]" type="checkbox" value="1" />
  <input name="flight[meal]" type="hidden" value="0" /><b>Meal Service</b>
</p>

The first <input> control is our checkbox. Notice again how it uses the “model[attribute]” convention to help populate the params hash for later processing. If the checkbox is selected, the browser will send the value “1”.

If the checkbox isn’t selected, browsers won’t send “0”. In fact, they won’t send anything at all! This is a problem, because it means that if we were editing this flight, and you unchecked the box to turn off meal service, we would never know it, because the flight[meal] value would not exist in the params hash.

To get around this, the check_box method we used automatically inserts that second, hidden input tag, with the same name but a value of zero. If the checkbox is not checked, this input value will be sent by the browser and our Rails code will update the model accordingly.

Go ahead and check the box, and then click the Create button. You’ll create a new flight, and this time the meal column will have the value true. We can learn how to use the new meal? attribute by opening up the Rails console again and finding our new flight:

>> f = Flight.find_by_number '987'
=> #<Flight id: 2, number: "987", created_at: "2008-04-05 20:24:21", updated_at: "2008-04-05 20:24:21", meal: true>
>> f.meal?
=> true

Ta da! A checkbox that’s hooked up to the meal column, just like we hooked up our textbox to the number column last time. .NET readers may begin to see that form_for is how we do “data binding” in Rails.

Radio Radio

Next time, we’ll be adding some radio buttons to our form. In the meantime, if you have comments or questions on what we’ve done so far, leave us a comment below.