The Cookbook Application, cont.
To bring up the Cookbook application, create a new Ruby project (right-click in Package Explorer view, then New Project.
We name the project “cookbook” and then deselect the “Use default location” checkbox.
This allows us to use the cookbook code that was downloaded with InstantRails, instead of a new copy of that code. Why is this important?
We find the downloaded code in C:\InstantRails\rails_apps\cookbook.
Click “OK” and then “Finish”.
Then launch InstantRails.
Click the little beige “I” box in the upper right-hand corner, and choose “Rails Applications > Open Ruby Console Window”. This gives you a terminal window:
Type “cd cookbook”, then “ruby script\server”. This starts the Web server.
Then we can bring up a Web browser and browse to which redirects to
We can then proceed to exercise the application as we did in the last lecture. But now we can change its functionality too.
Let’s make a trivial change: Change “Show all recipes” and “Show all categories” to “List all recipes and “List all categories”.
Where shall we make this change?
Well, if we click on “Show all categories”, we get this screen.
What do you notice about the bottom line?
And if we click on “Create new recipe”, we get this screen:
Again, the bottom line is the same. Let’s take a look at the directory listing. Which file do you think contains that code?
This is an .rhtml file, the first time we’ve seen this type. What do you think it stands for?
Let’s look at the code in this file.
<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>
This raises several questions.
- What gets invoked when we click on the “Show all recipes” link?
- How do we make it say, “List all recipes”?
- What does @content_for_layout do?
- What’s that strange notation surrounding @content_for_layout?
- Where is it specified that a standard layout is used for certain view screens?
And if we poke around and look at all the views, we will find that all have the same “navigation bar” at the bottom.
Now, to make sure you are following, here are some review questions.
- What is the function of “ruby script/server”?
- What URL do we type in to find the homepage of our cookbook application?
- When we click on “Show all categories”, what URL will be taken to?
- What are the filenames that contain views associated with recipes?
- What is Embedded Ruby, and how have we seen it used?
- What kind of relationship is there between recipes and categories?
- Where is this relationship represented?
The views
Now, let’s take a look at the View code. We’ll look at it line by line, which may obscure the flow, but if you have trouble, just look in your rails_apps/cookbook directory for the uncommented code.
<form action="../update/<%= @recipe.id %>"method="POST"
There are three main components here.
- The <form> tag
- The ERB code <%= @recipe.id %>
- The POST method.
<input id="recipe_id"name="recipe[id]"size="30"
type="hidden"value="<%= @recipe.id %>" />
You probably won’t figure this out, so I’ll tell you …
- recipe_id is a field in the recipes table of the database.
- recipe[id] is a field of a hash called “recipe“ that will be passed back as POST parameter.
- This is a hidden field because the ID (the primary key of the recipes table) has no reason to appear on the form that edits a recipe. However, it does need to be passed to the update method, so that method knows which row of the table to update.
- You should be able to figure out what the ERb code @recipe.id means, but where is that instance variable set?
<p<b>Title</b<br>
<input id="recipe_title"name="recipe[title]"size="30"
type="text"value="<%= @recipe.title %>" />
</p>
Can you explain this, by analogy with the previous <input> tag?
The next field is very similar …
<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>
Now we use a <select> form to put up a list (dropbox) of categories.
<select name="recipe[category_id]"
<% @categories.eachdo |category| %>
<option value="<%= category.id %>"
<%= ' selected'if category.id == @recipe.category_id %>
<%= category.name %>
</option>
<% end %>
</select</p>
What does 'selected' do here?
<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>
The rest should be fairly self-explanatory.
list.rhtml
Now that we have seen one view, the next one is not very different.
It begins with some garden-variety HTML
<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>
Then we cycle through each recipe, displaying it, if that is the appropriate thing to do. Where did the @recipes instance variable come from?
How is it deciding whether to display a recipe?
<% @recipes.eachdo |recipe| %>
<% if (@category == nil)
|| (@category == recipe.category.name)%>
<tr>
Where does the @category variable come from?
Let’s consider each of the parameters of the link_to calls below.
- The first link is to a scaffold method (which we can consider to be magic for now, but if you really want to see it, from a command window, execute ruby script/generate scaffold <model> <controller>, where <model>and<controller> are both recipe in this case).
<td>
<%= link_to recipe.title,
:action => "show",
:id => recipe.id %>
- The next link is to the delete method, which has a :confirm parameter. This is a feature of Rails, which pops up a dialog box for confirmation before deleting the recipe.
<font size=-1>
<%= link_to "(delete)",
{:action => "delete", :id => recipe.id},
:confirm => "Really delete #{recipe.title}?" %>
</font>
</td>
- The next link displays recipe.category.name and passes two parameters. What is the :category parameter?
<td>
<%= link_to recipe.category.name,
:action => "list",
:category => "#{recipe.category.name}" %>
</td>
<td<%= recipe.date %</td>
</tr>
<% end %>
<% end %>
</table>
new.rhtml
The only remaining controller is new.rhtml. It doesn’t illustrate much that we haven’t seen before, so I’ll ask you the questions (below).
<form action="/recipe/create"method="post"
<p>
<b>Title</b<br/>
<input id="recipe_title"name="recipe[title]"
size="30"type="text"value=""/>
</p>
<p>
<b>Description</b<br/>
<input id="recipe_description"
name="recipe[description]"
size="30"type="text"value=""/>
</p>
<p>
<b>Category:</b<br/>
<select name="recipe[category_id]"
<% @categories.eachdo |category| %>
<option value="<%= 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"
</textarea>
</p>
<input type="submit"value="Create"/>
</form>
- Which controller is invoked when the form is submitted? Where is the code for this controller?
- Where, and in what form, is recipe[title] sent?
- What is the function of the <% @categories.eachdo |category| %> loop?
- What link or button do we press to submit this form?
Active Record
Our Ruby on Rails programs deal with objects, but they are mapped into relational databases.
There’s a mismatch here—how are the database tables translated into objects, and how are objects created in the program saved to the db?
Ruby on Rails’ solution is Active Record. In Active Record,
- Database tables correspond to Rails classes.
- Database records (rows) correspond to Rails objects.
We can perform operations on tables by invoking class methods, as is done in the RecipeController:
@recipes = Recipe.find_all
@categories = Category.find_all
There are also methods that operate on database records. These are invoked on objects. Can you find some in the code below?
class RecipeController < ApplicationController
layout "standard-layout"
scaffold :recipe
def new
@recipe = Recipe.new
@categories = Category.find_all
end
def list
@category = @params['category']
@recipes = Recipe.find_all
end
def edit
@recipe = Recipe.find(@params["id"])
@categories = Category.find_all
end
def create
@recipe = Recipe.new(@params['recipe'])
@recipe.date = Date.today
if @recipe.save
redirect_to :action => 'list'
else
render_action 'new'
end
end
def delete
Recipe.find(@params['id']).destroy
redirect_to :action => 'list'
end
end
Notice also that the fields of an object correspond directly to the fields of a database record. This means that Ruby code can directly manipulate fields in database records (well, it can retrieve them via , manipulate them, and save them back via ).
Lecture 13Object-Oriented Languages and Systems1