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
int(6) to match the key of the
Figure 48 has the details.
Figure 48. The
recipe table with its new
This will hold the
id of the recipe's category. Now tell the
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:
Figure 49. Setting relationships in the
Figure 50. Setting relationships in the
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
if I have a category object in
@category, I can fetch a collection
of all recipes in that category using the code
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
method like in Figure 51.
Figure 51. The
Recipe controller's new
This creates two instance variables that the template will use to
render the "edit recipe" page.
@recipe is the recipe that we want to
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
<option> tags that create the drop-down list of
<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
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.
and edit the recipe for "Ice Water." Change its category to "Beverages," as
shown in Figure 52.
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
You should see something like Figure 53.
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.
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!
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>
- Official Ruby home page
- Official Ruby on Rails home page
- The best way to learn Ruby: read the free book Programming Ruby
- Primary home for open source Ruby projects: RubyForge
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 O'Reilly Ruby.