NOTE to Reader:    
A Rails web application can run under virtually any web server.  The Apache web server comes packaged with and is configured for use in the normal course of installing and configuring Instant Rails.  Unfortunately, depending on what other software you have installed, configured, and/or running on your system, Apache's configuration may need to be "tweaked."  The way to develop a Rails web application with the least probability of "hitting a snag" is to use the built-in WEBrick web server. So that's the web server this tutorial will use.  If it's important to you that your application use Apache, and you think we should have another tutorial that's specific to Apache, let us know by adding a comment to the tutorial's wiki page and/or posting to the Instant Rails forum on RubyForge.org.

Bill Walton
1/20/2006

---------  Revision History ----------
-Original
     Curt Hibbs
     http://www.onlanp.com/pub/a/onlamp/2005/01/20/rails.html
     01/20/2005

- Modified to make it specific to InstantRails Release 1.0 on Win2K SP4
     Bill Walton
     01/21/2006
-------- End Revision History ---------


Rolling with Ruby on [Instant]Rails, Release 1.0 on Win2K SP4

Maybe you've heard about Ruby on Rails, the super productive new way to develop web applications, and you'd like to give it a try, but you don't know anything about Ruby or Rails. This article steps through the development of a web application using Rails. It won't teach you how to program in Ruby, but if you already know another object-oriented programming language, you should have no problem following along (and at the end you can find links on learning Ruby).

Let's answer a couple of burning questions before rolling up our sleeves to build a web application!

What is Ruby?

Ruby is a pure object-oriented programming language with a super clean syntax that makes programming elegant and fun. Ruby successfully combines Smalltalk's conceptual elegance, Python's ease of use and learning, and Perl's pragmatism. Ruby originated in Japan in the early 1990s, and has started to become popular worldwide in the past few years as more English language books and documentation have become available.

What is Rails?

Rails is an open source Ruby framework for developing database-backed web applications. What's special about that? There are dozens of frameworks out there and most of them have been around much longer than Rails. Why should you care about yet another framework?

What would you think if I told you that you could develop a web application at least ten times faster with Rails than you could with a typical Java framework? You can--without making any sacrifices in the quality of your application! How is this possible?

Part of the answer is in the Ruby programming language. Many things that are very simple to do in Ruby are not even possible in most other languages. Rails takes full advantage of this. The rest of the answer is in two of Rail's guiding principles: less software and convention over configuration.

Less software means you write fewer lines of code to implement your application. Keeping your code small means faster development and fewer bugs, which makes your code easier to understand, maintain, and enhance. Very shortly, you will see how Rails cuts your code burden.

Convention over configuration means an end to verbose XML configuration files--there aren't any in Rails! Instead of configuration files, a Rails application uses a few simple programming conventions that allow it to figure out everything through reflection and discovery. Your application code and your running database already contain everything that Rails needs to know!

Seeing is Believing

We developers often hear the excessive hype that always seems to accompany something new. I can just imagine that skeptical look on your face as you hear my dubious claims. Ten times faster development, indeed!

I'm not asking you to accept this on blind faith. I'll show you how to prove it to yourself. First, I'll install the needed software. Then I will lead you through the development of a web application.

We'll develop this web application on Windows. You can still follow along if you use a Linux or Macintosh system, but your screen will look different from the screen shots shown below and you will have to install software packages built specifically for your system. See the Resources section at end of this article for additional software links.

The tutorial assumes you begin with the following environment.

To complete the tutorial you will need two additional pieces of software: The tutorial is organized into three sections.  The first section of the tutorial walks you through installing the new software and verifying that the Rails development environment is functioning properly and ready for use.  The second section illustrates, just for clarity's sake, the steps to start the development environment for sessions after the installation. The final section is where you'll find the beef.  There, in record time, we'll develop a database-driven web-based cookbook application using Ruby on Instant Rails.

Installing and Configuring the Software

Step 1.  Install Instant Rails

Installing Instant Rails takes two steps.
  1. Download the "Instant Rails 1.0 Final" release.
  2. UnZip the download (InstantRails-1.0-rc1-win.zip) onto your C: drive.
Click here for detailed instructions on the above.

Step 2.  Install MySQL-Front

MySQL-Front is a graphical interface for the MySQL database. It is an inexpensive commercial application, but you can try it for free for 30 days. In this article, we will use MySQL-Front to develop our database. If you prefer, you can also just send SQL commands to the database from the command line.

  1. Download the latest version of MySQL-Front (version 3.2 was used for this tutorial).
  2. Install the software by double-clicking on the downloaded executable and following the installation instructions. The tutorial assumes the default setup.

Step 3.  Complete and verify the setup

You are just a few simple steps away from writing code.   Click here for instructions on completing the setup and verifying the functionality of your Rails development environment.  

There is a working version of the cookbook application you'll create with this tutorial already included in the Instant Rails download.  We'll use it to verify the functionality of our development environment.

Our cookbook can:

Use the application some before you move on to get familiar with what you'll be creating.

Restarting the Development Environment

In the section above you both installed and configured the development environment software and started the development environment.  This section details only the steps to start the development environment.  It is furnished as a reference for your future use.

Click here to see the details of restarting the Development.  
As a quick ref. in my shorthand, the steps I use are:
1) use_ruby
2) InstantRails -> stop Apache
3) Manage Rails apps -> start cookbook with WEBrick
4) http://localhost:3000/recipe/list
5) stop WEBrick

NOTE:  Even though I could stop after step 2, I always take a minute to fire up the cookbook app just to verify the environment is working correctly before I begin development.  That way, if something isn't working the way I expected, I'm less likely to waste time trying to figure out what's wrong with my code when that's not where the problem is.

Let's Write Code!

Creating an Empty Rails Web Application

Rails is both a runtime web app framework and a set of helper scripts that automate many of the things you do when developing a web application. In this step, we will use one such helper script to create the entire directory structure and the initial set of files to start our cookbook application.

Open a Command Prompt window and navigate to where you want to create this cookbook web application. I used c:\Instantrails\rails_apps.  (which is where 'use_ruby.cmd' leaves us when we restart the development environment).

Run the command:
rails cookbook2

This will create a cookbook2 subdirectory containing a complete directory tree of folders and files for an empty Rails application.


Figure 5. Rails at work creating the new application directory

Testing the Empty Web Application

You've seen how to start WEBrick using Instant Rails.  We can also start this web server from the command line.  Let's do that and then browse to our cookbook application.  In your open command window, 

          cd cookbook2


Figure 6. Starting the WEBrick server

Leave the Command Prompt window open and the web server running, as we will be using it as we proceed.

Now open your browser and browse to http://localhost:3000/. You should see something like Figure 8. [Editor's note: Unless you're following along with the article, these links probably won't work for you. Don't panic--localhost, a.k.a. 127.0.0.1, is a special address reserved for the local machine.]


Figure 7. The Rails default page

A Rails Application's Directory Structure

Rails tries very hard to minimize the number of decisions you have to make and to eliminate unnecessary work. When you used the rails helper script to create your empty application, it created the entire directory structure for the application (Figure 9). Rails knows where to find things it needs within this structure, so you don't have to tell it. Remember, no configuration files!


Figure 9. A Rails application directory structure

Most of our development work will be creating and editing files in the c:\Instantrails\rails_apps\cookbook2\app subdirectories. Here's a quick rundown of how to use them.

Controllers and URLs

In a moment, we will create our cookbook database and begin developing our application. First, it's important to understand how controllers work in Rails and how URLs map into (and execute) controller methods.

Controller classes handle web requests from the user. The URL of the request maps to a controller class and a method within the class. How does this work?

Leave your existing command window open with the web server running, and open a second command window.  Enter >use_ruby and then navigate to the application's base directory: c:\Instantrails\rails_apps\cookbook2. It will look like Figure 10, at least in a moment.


Figure 10. The cookbook controller directory

We will use another Rails helper script to create a new controller class for us. In the command window, run the command:

ruby script\generate controller MyTest

This will create a file named my_test_controller.rb containing a skeleton definition for the class MyTestController.

In the c:\Instantrails\rails_apps\cookbook2\app\controllers directory, open the my_test_controller.rb  file with your text editor. The file should resemble Figure 11.


Figure 11. Editing MyTestController

What happens if you browse to something that you know does not exist? Try http://localhost:3000/garbage/. Figure 12 shows the results.


Figure 12. Browsing to an unknown controller

That's not too surprising. Now try http://localhost:3000/My_Test/, shown in Figure 13.


Figure 13. Browsing to the new controller

Hmmm. Now that's different. The MyTest part of the URL maps to the newly created controller. Now it seems that Rails tried to find an action named index in this controller but couldn't.

Let's fix that. Add an index method to your controller class as in Figure 14.


Figure 14. The index method of MyTestController

Refresh your browser, and you should now see something more like Figure 15.


Figure 15. The result of the index method

You will have the same results with http://127.0.0.1:3000/My_Test/index, too.

Let's add another action to the controller just to make sure you have the idea. Add the dilbert method from Figure 16.


Figure 16. The dilbert method

Now browse to http://localhost:3000/My_Test/dilbert and you'll see something like Figure 17.


Figure 17. The output of the dilbert method

I think you have the idea.

Let's create our database now and work on some real pieces of our cookbook application.

Creating the Cookbook Database

It's time to create the cookbook2 database and tell Rails how to find it. (This is the only configuration that you will find in Rails.)

  1. Start MySQL-Front and log in to your locally running MySQL instance (localhost) as root, using an empty password. After logging in, you should see something like Figure 18.


    Figure 18. MySQL-Front

  2. Ignore the existing databases for now. Create a new database named cookbook2. Execute the menu command Database>New>Database... and enter the database name cookbook2, as Figure 19 illustrates.


    Figure 19. Creating a new database

    Click Ok to create the database.

  3. To tell Rails how to find the database, edit the file c:\Instantrails\rails_apps\cookbook2\config\database.yml and change the database name to cookbook2. Insert the 'host: localhost" line.  Leave the username as root and the password empty. When you finish, it should look something like Figure 20.


    Figure 20. The database.yml configuration file

Rails lets you run in development mode, test mode, or production mode, using different databases. This application uses the same database for each.

Important Note:  With Rails 1.0 you must restart the web server after any changes to the database.yml file.  If you do not, Rails will never see the new database.  Thus, the steps below will fail. To restart WEBrick, bring its Command Prompt window into focus and either hit Ctrl-C or simply close the window as it suits you.  Re-launch the WEBrick web server by opening a new Command Prompt window and running "ruby script\server" from the cookbook2 directory as above.

Creating the recipes Table

Our cookbook will contain recipes, so let's create a table in our database to hold them.

In the left-hand pane of MySQL-Front, right click on the cookbook database you just created and select New>Table... from the pop-up menu (Figure 21).


Figure 21. Creating a new table

Name the table recipes (Figure 22).


Figure 22. The Add Table dialog box

Important note: MySQL-Front will automatically create a primary key.  Sometimes it's named "Id" and sometimes its "id".  Rails prefers to call it id (all lowercase). I'll explain more later, but for now just change it if you need to. In the left pane, select the recipes table you just created. In the right pane, right-click on the Id field, select Properties (Figure 23), and change the name to id.


Figure 23. Renaming the primary key

Adding Recipe Fields

Now that we have a recipes table, we can start adding fields (columns) to hold recipe data. Let's start by creating title and instructions fields. Eventually, we will need more fields, but this is a good place to start.

With the recipes table selected, right click in a blank area of the right pane and select New>Field... (Figure 24).


Figure 24. Adding a new field

Create the recipe title field as a varchar(255) with nulls not allowed, so that every recipe must have title text. Figure 25 shows the options in the pop-up window.


Figure 25. Adding the title field

Repeat the above procedure to create an instructions field as text, as shown in Figure 26.


Figure 26. Adding the instructions field

The recipe table should now resemble Figure 27.


Figure 27. The modified recipe table

The Excitement Begins

Everything we have done up to this point has been pretty short and painless, but not particularly exciting. This is where that changes. We can now have the very beginnings of our cookbook application up and running in record time!

Create the Model

First, create a Recipe model class that will hold data from the recipes table in the database. Figure 28 shows where it should live.


Figure 28. The Recipe model class

Open a command window to the cookbook2 directory (c:\Instantrails\rails_apps\cookbook2) and run the command:

ruby script\generate model Recipe

This will create a file named recipe.rb containing a skeleton definition for the Recipe class. Open this file with your text editor and look inside (Figure 29).


Figure 29. The contents of recipe.rb

This seemingly empty class definition is the recipe business object that Rails maps to the recipes table in the database. You will see more concretely what I mean by this in a moment. Right now, I want to point out that this little bit of programming magic happened because we used a Rails naming convention: a singular model class name (Recipe) maps to a plural database table (recipes). Rails is smart about English pluralization rules, so Company maps to companies, Person maps to people, and so forth.

Further, Rails dynamically populates the Recipe class with methods for accessing the rows in the recipes table and an attribute for each column in the table.

Very shortly, you will see a dramatic demonstration of this dynamic connection between the Recipe class and the recipes table.

We are now very close to seeing something work. We need to create a recipe controller (Figure 30) with actions to manipulate the recipes in the database via the standard CRUD operations: create, read, update, and delete. Rails makes this easier than you might think.


Figure 30. The Recipe controller in its native environment

Open a command window in the cookbook directory (c:\Instantrails\rails_apps\cookbook2) and run the command:

ruby script\generate controller Recipe

This will create a file named recipe_controller.rb containing a skeleton definition for the RecipeController class. Right-click on this file, choose Edit, and add the line scaffold :recipe as shown in Figure 31.


Figure 31. One line of code in RecipeController

This single line of code will bring the database table to life. It defines actions for all CRUD operations, immediately allowing us to create, read, update, and delete recipes in our database!

Open a browser and navigate to http://localhost:3000/recipe/new. You should see something like Figure 32.


Figure 32. Creating a new recipe page

Now this is pretty cool! We haven't done much of anything and we can already start to populate our database. Don't do that just yet, though. Let's add a few more fields to the recipe table first.

Use MySQL-Front to add description and date fields between the title and instructions fields (Figures 33 and 34).


Figure 33. Adding the description field


Figure 34. Adding the date field

Refresh your browser to see a page similar to Figure 35.


Figure 35. A new recipe page with the new fields

Now, that is way beyond cool--it's awesome!

OK, calm down and enter a test recipe. Fill in the fields as shown in Figure 36 and click on the Create button.


Figure 36. A new recipe

You should see the results, as in Figure 37.


Figure 37. A listing of all recipes

Add another one by clicking the "New recipe" link and entering the data, as in Figure 38.


Figure 38. Another new recipe

After you click Create you should see something like Figure 39.


Figure 39. A fuller list of all recipes

We now have an amazing amount of functionality, by merely building a database table and typing in a single line of code. It may not be pretty yet, but we'll fix that soon enough.

In the meantime, play around with adding, deleting, and editing recipes. Go ahead; I'll wait for you in the next section.

What Just Happened?

A single line of code, scaffold :recipe, brought everything to life. It let us begin working with our data model. With virtually no work on our part, it created the actions list, show, edit, and delete. It also created default view templates for each of these actions.

Of course, these actions and views are very plain--not the sort of thing you'd want users to see (unless they are total geeks). The good news is that we can leave the scaffolding in place and slowly, one at a time, provide our own versions of the actions and views. Each time you create one of the actions or views it will override the scaffold's version. When you're done, simply remove the scaffold statement from the controller.

Before we do that, did you notice the URLs as you were playing around with your new cookbook? Rails tries very hard to present the user with pretty URLs. Rails URLs are simple and straightforward, not long and cryptic.

Creating Actions and Views

The page that shows the list of all recipes (Figure 39 above) desperately needs improvement. The way to do that is to take over the handling of the list action from the scaffolding.

Edit recipe_controller.rb and add a list method similar to Figure 40.


Figure 40. A new list method

Browse to http://localhost:3000/recipe/list and you should see something like Figure 41.


Figure 41. The results of the new list method

Because we just created our own definition for the list action, Rails no longer uses the scaffold version. Rails called our list method and then tried to find a view template to render. Because we did not create one, we received this "template missing" error. Let's create our own view template for the list action that only shows each recipe's title and date.

When we created our recipe controller, the generate controller script also created a view directory where we can place the HTML templates that the recipe controller can display. We need to create a template file named list.rhtml. If you have worked with JSP or ASP pages, this will look familiar. It is simply an html file with Ruby code embedded within <% %> and <%= %> tags.

In the directory C:\Instantrails\rails_apps\cookbook2\app\views\recipe, create a file named list.rhtml containing the following:

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

<h1>Online Cookbook - All Recipes</h1>
<table border="1">
<tr>
<td width="80%"><p align="center"><i><b>Recipe</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.date %></td>
</tr>
<% end %>
</table>
<p><%= link_to "Create new recipe", :action => "new" %></p>

</body>
</html>

Edit recipe_controller.rb and add the single line of code shown in Figure 42 to the list method.


Figure 42. Listing all recipes

Refresh your browser and you should see something like Figure 43.


Figure 43. A nicer recipe list

Now this definitely looks better! How does it work?

When a user browses to http://localhost:3000/recipe/list Rails will call the new list method we just created. The single line of code in the method asks the Recipe class for a collection of all recipes from the database, assigning the collection to the instance variable @recipes.

Next, Rails will look for a template to render and return to the browser. Most of our list template is standard HTML. The real action is in this section of the template:

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

This embedded Ruby code iterates through the collection of recipes retrieved in the controller. The first cell of the table row creates a link to the recipe's show page. Notice the attributes used on the recipe object (title, id, and date). These came directly from the column names in the recipes table.

Adding Categories to the Cookbook

We want to be able to assign a recipe to a category (like "dessert") and be able to list only those recipes that are in a particular category. To do this, we need to add a category table to the database, and a field to the recipe table specifying the category to which the recipe belongs.

In MySQL-Front, create a categories table. Remember to change the automatically created Id field to id, and then create a name field as a VarChar(50).  Do not allow NULL values.  The resulting table should look like Figure 44.


Figure 44. The categories table

We also need a category controller and a category model. Open a new command window if you need to, run use_ruby.cmd to set the PATH and move you into the cookbook2 directory, then run the commands:

ruby script\generate controller Category
ruby script\generate model Category


Figure 45. Creating the category model and controller

Finally, add scaffolding to the category controller. Edit c:\rails\cookbook\app\controllers\category_controller.rb and add the scaffolding shown in Figure 46.


Figure 46. Category scaffolding

Browse to http://localhost:3000/category/list and create two categories named Snacks and Beverages. When you are done, you should see something like Figure 47.


Figure 47. A listing of all categories

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 recipes table.  Make sure the Type is the same as the  'id' field of the categories table (e.g., int(11) )  The default Type that mySQL uses for 'id' may vary, so double check..  Figure 48 has the details.


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:\Instantrails\rails_apps\cookbook2\app\models\recipe.rb and c:\Instantrails\rails_apps\cookbook2\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 Recipe 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:\Instantrails\rails_apps\cookbook2\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:\Instantrails\rails_apps\cookbook2\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://localhost:3000/recipe/list , click on "Ice Water", then click on "Edit".  Assign it to the "Beverages" category as shown in Figure 52.


Figure 52. Changing the category for a recipe

Before moving on to the final step, make sure you assign a category to all the recipes you've entered in the database. 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 ..\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 browse to http://localhost:3000/recipe/list  You should see something like Figure 53.

recipes listed with categories
Figure 53. Recipes listed with categories

Congratulations!

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


It's time to round out the recipe application a bit. After that, I'll present some other features of Rails that I'm sure you'll want to know about.

Remember that we created our cookbook application in the directory c:\Instantrails\rails_apps\cookbook2; all paths used in this tutorial assume this base directory. Additionally, to improve readability and since all the work from here out will involve changes to the files contained in the apps directory one level down, we will use the "..\" shorthand to signify c:\Instantrails\rails_apps\cookbook2\apps .  So when you see an instruction, for example, to edit the "..\controllers\recipe_controller.rb" file you will know that means the file with the fully qualified path equal to "c:\Instantrails\rails_apps\cookbook2\apps\controller\recipe_controller.rb"

If you chose to develop you app in a different location, please be sure to make the proper adjustments to the application paths you see in this article.

Creating a new recipe with a category

Because the code still relies on the scaffolding to create new recipes, there is no way to assign a category to a recipe when we create it. This wouldn't be so bad--except that the page created to list all recipes assumes that every recipe will have a category, and it generates an error if this is not true. That means that the way we left things above, if you add a new recipe, you'll generate an error as soon as you click the "Create" button because the next thing the app does is to try to list all the recipes.  Try it and you'll get something like Figure 54.


Figure 54  What happens when you try to create a new recipe without a new method

IMPORTANT NOTE:  If you try to create a new recipe and get the error above, you need to take a moment and delete that record from the database.  If you do not, you will get these same errors below.  Click here for instructions if you need them.

The fix is to take over the new action from the scaffolding just as we already did with the edit action. Edit ..\controllers\recipe_controller.rb and add a new method like in Figure 55.

the Recipe controller's new method
Figure 55. The Recipe controller's new method

The code @recipe = Recipe.new creates a new, empty recipe object and assigns it to the instance variable @recipe. Remember, an instance of the Recipe class represents a row in the recipes database table. When creating a new recipe object, the Recipe class can assign default values for each field that the view template can use.

The Recipe model class doesn't currently set any such default values, but the view template I'll show off momentarily will use whatever is in the @recipe object to initialize the display form. Later, you could add default values in the Recipe class that will show up when you create a new recipe.

As with the edit action, the new action also retrieves a collection of all categories so that it can display a drop-down list of categories from which the user can choose. The @categories instance variable holds this list of categories.

In the directory c:\Instantrails\rails_apps\cookbook2\app\views\recipe, create a file named new.rhtml that contains the HTML template shown below. It's mostly standard HTML, with some extra code to create the <select> and <option> tags for the drop-down list of categories:

<html>
<head>
<title>New Recipe</title>
</head>
<body>
<h1>New Recipe</h1>
<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.each do |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>
<a href="/recipe/list">Back</a>
</body>
</html>

This is not much different from the edit template we created above. I left the recipe's date out of this view because I'll set it to the current date when a user posts the form back to the web app. This ensures that the recipe's date will always be its creation date.

If you look at the form tag, you will see that this form will post to a create action in the recipe controller. Edit c:\Instantrails\rails_apps\cookbook2\app\controllers\recipe_controller.rb and add this create method:

def create
@recipe = Recipe.new(@params['recipe'])
@recipe.date = Date.today
if @recipe.save
redirect_to :action => 'list'
else
render_action 'new'
end
end

This method first creates a new recipe object and initializes it from the parameters posted by the form in new.rhtml. Then it sets the recipe's date to today's date, and tells the recipe object to save itself to the database. If the save is successful, it redirects to the list action that displays all recipes. If the save fails, it redirects back to the new action so the user can try again.

Give it a try. Start the web server by opening a command window, enter use_ruby to set the PATH and navigate to c:\Instantrails\rails_apps\cookbook2, run the command ruby script\server to start the WEBrick server. Then browse to http://localhost:3000/recipe/new and add a new recipe like the one shown in Figure 56.


Figure56. Adding a new recipe with a category

After you create the new recipe, you should see something like Figure 57.


Figure 57. List of all recipes

Deleting a recipe

You may not have noticed it, but once we took over the list action from the scaffolding we no longer had a way to delete a recipe. The list action must implement this. So we're 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  ..\views\recipe\list.rhtml and add the delete link by making it look like this:

<html>
<head></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},

It's also different from the previous version in another way. It uses an option that generates a JavaScript confirmation dialog. 
:confirm => "Really delete #{recipe.title}?" %>

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 refreshing your browser (or by browsing to http://localhost: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 58.


Figure 58. Confirm deleting the Ice Water recipe

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


Figure 59. 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 ..\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.  Refresh your browser and try to delete the Ice Water recipe. Now it should look like Figure60, and the Ice Water recipe should be gone.


Figure 60. Ice Water recipe is gone

Using layouts

To get us started, I 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 ..\controllers\recipe_controller.rb and add the layout line immediately after the class definition, as shown in Figure 61.


Figure 61. 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 ..\views\layouts\standard-layout.rhtml. 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 ..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 ..\views\recipe\edit.rhtml and ..\views\recipe\new.rhtml to delete the same extraneous lines. Because they are being "called" from the layout file, they no longer need to be fully formed HTML files with <html>, <head>, and <body> tags.  Only the form tags and everything in between should remain.

Browse to http://localhost:3000/recipe/list and it should look like Figure 62.


Figure 62. 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 ..\controllers\category_controller.rb and add the layout line as shown in Figure 63.


Figure 63. 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

Showing recipes in a category

Our final task is to add the ability to display only those recipes in a particular category. I'll take the category displayed with each recipe on the main page and turn it into a link that will display only the recipes in that category.

To do this, I'll change the recipe list view template to accept a URL parameter that specifies what category to display, or all categories if the user has omitted the parameter. First, I need to change the list action method to retrieve this parameter for use by the view template.

Edit ..\controllers\recipe_controller.rb and modify the list method to look like this:

def list
@category = @params['category']
@recipes = Recipe.find_all
end

Then edit ..\views\recipe\list.rhtml to 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| %>
<% if (@category == nil) || (@category == recipe.category.name)%>
<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>
<%= link_to recipe.category.name,
:action => "list",
:category => "#{recipe.category.name}" %>
</td>
<td><%= recipe.date %></td>
</tr>
<% end %>
<% end %>
</table>

There are two changes in here that do all the work. First, this line:

<% if (@category == nil) || (@category == recipe.category.name)%>

decides whether to display the current recipe in the loop. If the category is nil (there was no category parameter on the URL), or if the category from the URL parameter matches the current recipe's category, it displays that recipe.

Second, this line:

<%= link_to recipe.category.name, 
:action => "list",
:category => "#{recipe.category.name}" %>

creates a link back to the list action that includes the proper category parameter.

Make sure your recipe database contains entries for both Snacks and Beverages (rRemember that when we deleted the "Ice Water" recipe above, we were left with only Snacks)  Browse to http://localhost/recipe/list .   It should look something like Figure 64.


Figure 64.

Now click on one of the Snacks links. It should look like Figure 65.


Figure 65. Showing only snacks

What is it? How long did it take?

That's it! This is a reasonably functional online cookbook application developed in record time. It's a functional skeleton just begging for polish.

Wading through all of the words and screen shots in this article may have obscured (at least somewhat) exactly what this code can do and in what amount of developer time. Let me present some statistics to try to put it all into perspective.

Fortunately, Rails has some built-in facilities to help answer these questions. Open up a command window in the cookbook2 directory (c:\Instantrails\rails_apps\cookbook2) and run the command:

rake stats

Your results should be similar to Figure 66. Note that LOC means "lines of code."


Figure 66. Viewing development statistics

I won't give a detailed description of each number produced, but the last line has the main figure I want to point out:

Code LOC: 110

This says that the actual number of lines of code in this application (not counting comments or test code) is110. It took me about 30 minutes to create this application! I could not have come even close to this level of productivity in any other web app development framework that I have used.

Maybe you're thinking that this is an isolated experience using an admittedly trivial example. Maybe you're thinking that this might be OK for small stuff, but it could never scale. If you harbor any such doubts, the next section should lay those to rest.

Ruby on Rails Success Stories

Rails is a relatively young framework. As of this writing, it's been barely six months since the first public release. Yet it debuted with such a stunning feature set and solid stability that a vibrant developer community quickly sprang up around it. Within this time frame, several production web applications have been deployed that were built with Ruby on Rails.

Basecamp

From the site itself:

Basecamp is a web-based tool that lets you manage projects (or simply ideas) and quickly create client/project extranets. It lets you and your clients (or just your own internal team) keep your conversations, ideas, schedules, to-do lists, and more in a password-protected central location.

Basecamp was the first commercial web site powered by Ruby on Rails. David Heinemeier Hansson, the author of Rails, developed it. At its deployment, it contained 4,000 lines of code with two months of development by a single developer. In fall 2004, Basecamp stated that it had passed the 10,000-user mark. It considers the actual number of registered users to be proprietary information, but the home page currently states that it has "tens of thousands" of users.

43 Things

43 Things is a goal-setting social software web application. It currently has 6,000 registered users and hundreds of thousands of unregistered visitors. 43 Things has 4,500 lines of code that were developed in three months by three full-time developers.

Ta-da Lists

Ta-da Lists is a free online service that implements simple, sharable to-do lists. It features a highly responsive user interface that uses XMLHttpRequest to minimize waiting for the server. Ta-da Lists came from one developer using one week of development time producing 579 lines of code.

Snow Devil

Snow Devil is an e-commerce site specializing in snowboards and related equipment. It opened for business only recently, so there is no usage information available at this time. However, it comprises 6,000 lines of code created by two developers in four months.

CD Baby Rewrite

CD Baby is a very successful e-tailer of independent music. In business since 1998, it lists 82,443 artists that together have sold 1.2 million CDs, paying $12 million back into the artists' pockets.

The CD Baby web site previously involved an increasingly unmanageable 90,000 lines of PHP code. Its authors are in the process of rewriting it in Ruby on Rails. It's too early to find any development information, but the owner of CD Baby is publicly blogging about the process and progress of the conversion.

What does this all mean?

When all is said and done, good design will be more important than the framework in determining how your application performs. Think carefully about your database design and how its tables are indexed. Analyze your data access patterns and consider some strategic denormalization of data. Look for opportunities to cache preprocessed data.

Rails has a lot of powerful features to make it easy to prototype and develop applications quickly, which will leave you with more time to think about your application's features and how to tune it for performance.

A Smattering of Ruby on Rails' Features

Rails has many features that I have not used in this two-part article. I'd like to mention a few of them (with links to more information) to give you a more rounded view of the Rails toolkit.

Caching

Caching is cheap way to speed up your application by saving the results of previous processing (calculations, renderings, database calls, and so on) so as to skip the processing entirely next time. Rails provide three types of caching, in varying levels of granularity:

Validation and callbacks

To make sure your data is correct and complete before writing it to the database, you must validate it. Rails has a simple mechanism that allows your web application to validate a data object's data before the object updates or creates the appropriate fields in the database. Read the validation how-to or go straight to the validation API documentation.

ActiveRecord callbacks are hooks into the life cycle of a data object that can trigger logic before or after an operation that alters the state of the data object.

Transactions

ActiveRecord also supports transactions. Quoted straight from the documentation:

Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and vice versa. Transaction enforce the integrity of the database and guards the data against program errors or database break-downs. So basically you should use transaction blocks whenever you have a number of statements that must be executed together or not at all.

For example, consider the code:

transaction do
david.withdrawal(100)
mary.deposit(100)
end

Testing

Rails was built with testing in mind and provides support for testing your web application. An extensive online tutorial shows how to test a Rails web application.

Generators

Generators are the helper scripts that you can use to generate code for your application. You have already used generators to create new controllers and models, and at the beginning of this article I showed you how to use a new generator to create scaffolding.

Rails also supports user-created add-on generators. For example, Tobias Luetke has written a Login Generator that creates all the code for easily adding authentication, users, and logins to your Rails app.

Security

By now, everyone should know the importance of good security in web applications. The Ruby on Rails web site has an online security manual that describes common security problems in web applications and how to avoid them with Rails.

Parting Thoughts

Rails is not your run-of-the-mill, proof-of-concept web framework. It is the next level in web programming, and the developers who use it will make web applications faster than those who don't; single developers can be as productive as whole teams. Best of all, it's available right now, under an MIT license.

I believe that there hasn't been an improvement in productivity like this in recent programming history.

Want more Ruby on Rails? See Ajax on Rails.

Resources

Web sites
Mailing lists

Curt Hibbs is a senior software developer in St. Louis with more than 30 years' experience in platforms, languages, and technologies too numerous to list.