Rolling with Ruby on Rails, Part 2
Pages: 1, 2, 3

Deleting a recipe

If you remember from Part 1, once I took over the list action from the scaffolding I no longer had a way to delete a recipe. The list action must implement this. I'm going to add a small delete link after the name of each recipe on the main list page that will delete its associated recipe when clicked. This is easy.

First, edit c:\rails\cookbook\app\views\recipe\list.rhtml and add the delete link by making it 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 %>
        <font size=-1>
           
        <%= link_to "(delete)", 
                    {:action => "delete", :id => recipe.id},
                    :confirm => "Really delete #{recipe.title}?" %>
        </font>
        </td>
        <td><%= recipe.category.name %></td>
        <td><%= recipe.date %></td>
      </tr>
    <% end %>
   </table>
 <p><%= link_to "Create new recipe", :action => "new" %></p> 
 </body>
</html>

The main change here is the addition of this link:

<%= link_to "(delete)", {:action => "delete", :id
=> recipe.id},
:confirm => "Really delete #{recipe.title}?" %>

This is different from the previous ones. It uses an option that generates a JavaScript confirmation dialog. If the user clicks on OK in this dialog, it follows the link. It takes no action if the user clicks on Cancel.

Try it out by browsing to http://127.0.0.1:3000/recipe/list. Try to delete the Ice Water recipe, but click on Cancel when the dialog pops up. You should see something like Figure 5.

confirm deleting the ice water recipe
Figure 5. Confirm deleting the Ice Water recipe

Now try it again, but this time click on OK. Did you see the results shown in Figure 6?

error deleting the ice water recipe
Figure 6. Error deleting the Ice Water recipe

Alright, I admit it; I did this on purpose to remind you that it's OK to make mistakes. I added a link to a delete action in the view template, but never created a delete action in the recipe controller.

Edit c:\rails\cookbook\app\controllers\recipe_controller.rb and add this delete method:

def delete
    Recipe.find(@params['id']).destroy
    redirect_to :action => 'list'
end

The first line of this method finds the recipe with the ID from the link, then calls the destroy method on that recipe. The second line merely redirects back to the list action.

Try it again. Browse to http://127.0.0.1:3000/recipe/list and try to delete the Ice Water recipe. Now it should look like Figure 7, and the Ice Water recipe should be gone.

ice water recipe is gone
Figure 7. Ice Water recipe is gone

Using layouts

Part 1 used Rails' scaffolding to provide the full range of CRUD operations for categories, but I didn't have to create any links from our main recipe list page. Instead of just throwing in a link on the recipe list page, I want to do something more generally useful: create a set of useful links that will appear at the bottom of every page. Rails has a feature called layouts, which is designed just for things like this.

Most web sites that have common headers and footers across all of the pages do so by having each page "include" special header and footer text. Rails layouts reverse this pattern by having the layout file "include" the page content. This is easier to see than to describe.

Edit c:\rails\cookbook\app\controllers\recipe_controller.rb and add the layout line immediately after the class definition, as shown in Figure 8.

adding a layout to the recipe controller
Figure 8. Adding a layout to the recipe controller

This tells the recipe controller to use the file standard-layout.rhtml as the layout for all pages rendered by the recipe controller. Rails will look for this file using the path c:\rails\cookbook\app\views\layouts\standard-layout.rhtml, but you will have to create the layouts directory because it doesn't yet exist. Create this layout file with the following contents:

<html>
 <head>
   <title>Online Cookbook</title>
 </head>
 <body>
   <h1>Online Cookbook</h1>
   <%= @content_for_layout %>
   <p>
     <%= link_to "Create new recipe", 
                 :controller => "recipe", 
                 :action => "new" %>
     
   <%= link_to "Show all recipes", 
               :controller => "recipe", 
               :action => "list" %>
     
   <%= link_to "Show all categories", 
               :controller => "category", 
               :action => "list" %>
   </p>
 </body>
</html>

Only one thing makes this different from any of the other view templates created so far--the line:

<%= @content_for_layout %>

This is the location at which to insert the content rendered by each recipe action into the layout template. Also, notice that I have used links that specify both the controller and the action. (Before, the controller defaulted to the currently executing controller.) This was necessary for the link to the category list page, but I could have used the short form on the other two links.

Before you try this out, you must perform one more step. The previous recipe view templates contain some HTML tags that are now in the layout, so edit c:\rails\cookbook\app\views\recipe\list.rhtml and delete the extraneous lines at the beginning and end to make it look like this:

<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 %>
     <font size=-1>
        
     <%= link_to "(delete)", 
                 {:action => "delete", :id => recipe.id},
                 :confirm => "Really delete #{recipe.title}?" %>
     </font>
     </td>
     <td><%= recipe.category.name %></td>
     <td><%= recipe.date %></td>
   </tr>
 <% end %>
</table>

Similarly, edit both c:\rails\cookbook\app\views\recipe\edit.rhtml and c:\rails\cookbook\app\views\recipe\new.rhtml to delete the same extraneous lines. Only the form tags and everything in between should remain.

Browse to http://127.0.0.1:3000/recipe/list, and it should look like Figure 9.

using a layout with common links
Figure 9. Using a layout with common links

The three links at the bottom of the page should now appear on every page displayed by the recipe controller. Go ahead and try it out!

If you clicked on the "Show all categories" link, you probably noticed that these nice new links did not appear. That is because the category pages display through the category controller, and only the recipe controller knows to use the new layout.

To fix that, edit c:\rails\cookbook\app\controllers\category_controller.rb and add the layout line as shown in Figure 10.

adding a layout to the recipe controller
Figure 10. Adding a layout to the category controller

Now you should see the common links at the bottom of all pages of the recipe web application.

Pages: 1, 2, 3

Next Pagearrow