Rolling with Ruby on Rails
Pages: 1, 2, 3, 4, 5

Assigning a Category to Each Recipe

The cookbook now has recipes and categories, but we still need to tie them together. We want to be able to assign a category to a recipe. To do this we need to add a field to our recipes table to hold the category id for each recipe, and we'll have to write an edit action for recipes that provides a drop-down list of categories.

First, add a category_id field to the recipe table as an int(6) to match the key of the category table. Figure 48 has the details.

the recipe table with its new category_id
Figure 48. The recipe table with its new category_id

This will hold the id of the recipe's category. Now tell the Recipe model class about this too.

Edit c:\rails\cookbook\app\models\recipe.rb and c:\rails\cookbook\app\models\category.rb to add a single line to each model class, as shown in Figures 49 and 50:

setting relationships in the Recipe model
Figure 49. Setting relationships in the Recipe model

setting relationships in the
Category model
Figure 50. Setting relationships in the Category model

It should be pretty obvious that this tells Rails that a recipe belongs to a single category and that a category can have many recipes. These declarations actually generate methods to navigate these data relationships in Ruby code.

For example, if I have a recipe object in @recipe, I can find its category name with the code @recipe.category.name. Similarly, if I have a category object in @category, I can fetch a collection of all recipes in that category using the code @category.recipes..

Now it's time to take over the edit recipe action and template from the scaffolding so that we can assign categories. Edit c:\rails\cookbook\app\controllers\recipe_controller.rb and add an edit method like in Figure 51.

the Recipe controller's new edit method
Figure 51. The Recipe controller's new edit method

This creates two instance variables that the template will use to render the "edit recipe" page. @recipe is the recipe that we want to edit (the id parameter came in with the web request). @categories is a collection of all the categories in the database. The template will use it to create a drop-down list of category choices.

In the directory c:\rails\cookbook\app\views\recipe, create a file named edit.rhtml that contains the HTML template shown below. It's mostly standard HTML, with the main trick being the <select> and <option> tags that create the drop-down list of categories:

<html>
 <head>
  <title>Edit Recipe</title>
 </head>
 <body>
 <h1>Edit Recipe</h1>

 <form action="../update/<%= @recipe.id %>" method="POST"">
  <input id="recipe_id" name="recipe[id]" size="30" 
         type="hidden" value="<%= @recipe.id %>" />
  <p><b>Title</b><br>
  <input id="recipe_title" name="recipe[title]" size="30" 
         type="text" value="<%= @recipe.title %>" />
  </p>
  <p><b>Description</b><br>
  <input id="recipe_description" name="recipe[description]" 
         size="30" type="text" 
         value="<%= @recipe.description %>" />
  </p>
  <p><b>Category:</b><br>

  <select name="recipe[category_id]">
   <% @categories.each do |category| %>
       <option value="<%= category.id %>" 
         <%= ' selected' if category.id == @recipe.category_id %>>
         <%= category.name %>
       </option>
   <% end %>
  </select></p>

  <p><b>Instructions</b><br>
  <textarea cols="40" id="recipe_instructions" 
            name="recipe[instructions]" 
            rows="20" wrap="virtual">
    <%= @recipe.instructions %>
  </textarea> </p>
  <input type="submit" value="Update" />
 </form>

 <a href="/recipe/show/<%= @recipe.id %>">
   Show
 </a> | 
  <a href="/recipe/list">
  Back 
 </a>

 </body>
</html>

You can see the @recipe and @categories variables being used. Notice the section that loops through all of the categories to create a selection list. Look at the <option> tag and notice how it uses the current category assigned to the recipe being edited as the selected option. Study the template and then try it out.

Browse to http://127.0.0.1:3000/recipe/list and edit the recipe for "Ice Water." Change its category to "Beverages," as shown in Figure 52.

changing the category for a recipe
Figure 52. Changing the category for a recipe

Before moving on to the final step, make sure that all recipes in the database have a category. Edit each of them, select a category, and update them. If you don't do this, the next step will give you errors.

Displaying Categories in our List of All Recipes

This is the final step. Modify the list template that we made earlier to display each recipe's category.

Edit the file c:\rails\cookbook\app\views\recipe\list.rhtml to look like this:

<html>
<head>
<title>All Recipes</title>
</head>
<body>

<h1>Online Cookbook - All Recipes</h1>
<table border="1">
 <tr>
  <td width="40%"><p align="center"><i><b>Recipe</b></i></td>
  <td width="20%"><p align="center"><i><b>Category</b></i></td>
  <td width="20%"><p align="center"><i><b>Date</b></i></td>
 </tr>

 <% @recipes.each do |recipe| %>
  <tr>
   <td><%= link_to recipe.title, :action => "show", :id => recipe.id %></td>
   <td><%= recipe.category.name %></td>
   <td><%= recipe.date %></td>
  </tr>
 <% end %>
</table>
<p><%= link_to "Create new recipe", :action => "new" %></p>

</body>
</html>

Now try it by browsing to http://127.0.0.1:3000/recipe/list. You should see something like Figure 53.

recipes listed with categories
Figure 53. Recipes listed with categories

Exercises for the Reader

Congratulations, you've built a Rails web application! Of course, it still needs some work, but it is functional.

Here's some homework for you:

  • There is no longer any way to delete a recipe. Add a delete button (or link) to the edit template.
  • On the main recipes page, there aren't any links for the pages that let you manipulate categories. Fix that.
  • It would be nice to have a way to display only those recipes in a particular category. For example, maybe I'd like to see a list of all snack recipes or a list of all beverage recipes. On the page that lists all recipes, make each category name a link to a page that will display all of the recipes in that category.

Related Reading

Programming Ruby
The Pragmatic Programmer's Guide, Second Edition
By Dave Thomas

This article is the first of a two-part series. Part two will implement the items listed above, but you don't have to wait for me--implementing them yourself could be a fun way to start on Rails development!

Parting Thoughts

Ruby on Rails has taken web application development to a whole new level. You no longer need to do the parts that used to be tedious work, because Rails does them for you. Even if you have to use a legacy database that does not use the Rails naming conventions, you don't have to give up the productivity advantages of using Rails--there is still a way to tell Rails explicitly what table and column names to use.

Now that you've seen firsthand how easy it can be to create a web application, why would you want to do it any other way?

Perhaps your employer has mandated a particular framework or language. You can still take a few days to prototype it in Rails, then go to your boss and say, "I've already finished writing our entire application in Ruby on Rails! If you prefer, we can still take the next few months to write it as we originally planned." <grin>

Editor's note: Want more Ruby on Rails? See Rolling with Ruby on Rails, part 2 and Ajax on Rails.

Resources

Web Sites

Mailing Lists

Curt Hibbs has been a consultant to well-known companies like Hewlett Packard, Intuit, Corel, WordStar, Charles Schwab, Vivendi Universal, and more. He now works as a Senior Software Engineer for The Boeing Company in St. Louis.


Return to ONLamp.com.