TweetFollow Us on Twitter

Introduction to Ruby on Rails

Volume Number: 22 (2006)
Issue Number: 9
Column Tag: Ruby

Introduction to Ruby on Rails

by Rich Warren

Ruby on Rails (hereafter just called Rails) appears to be one of the newest hot-fad technologies for web design. It's a relatively new piece of technology, only 18 months old. And they've just released their 1.0 release. But, what is Rails?

On their web page (, they describe Rails as "a full-stack, open-source web framework in Ruby for writing real-world applications with joy and less code than most frameworks spend doing XML sit-ups" Sure, but what does that mean to the average web-app-programmer-on-the-street?

First, Rails is a web application framework. It is a collection of pre-packaged classes that work together to provide the skeleton for the entire web application. Rails automatically handles many of the tedious, repetitive tasks. For example, the ActiveRecord class automatically wraps most common database interactions. Just create the database's table, generate the corresponding model, and you will have objects to create, read, update and delete records (aka CRUD)--all without writing any code. Generate the corresponding controller, and you get ready-baked web pages for these actions (This does require a single line of code - just wait, we'll try it together). This means you spend less time churning out boilerplate, and more time implementing and improving features that make your clients happy. Less work for you, more joy for everyone.

Rails is specifically designed to implement web applications using a Model-View-Controller (MVC) architecture. This is the same basic MVC architecture used in many Cocoa applications. Basically you separate the application into independent components. The model component (represented by Rails's ActiveRecord class) requests data from and saves data to the data store. The model also validates the data and performs any necessary pre-or post-processing.

The View (represented by Rails's ActionView) displays the data. It produces the actual HTML seen in the user's browser. View files are simply *.rhtml files-basic HTML files, with additional Ruby scripting codes thrown in.

Finally the Controller (ActionController) handles incoming requests from the user's browser. It then requests the needed data from the model and funnels it to the proper view. The controller should also handle any business logic.

By keeping the components modular, it is easier to make changes to any one part of the web application, without touching the other two. You can also swap out one component for another. For example, you can easily create several different views of the same underlying data. The controller selects which view to display, based on the user's request.

Finally, Rails expects files to be stored in specific locations, and expects you to follow a set of naming conventions. For example, the table in the database always has a plural name (e.g. "messages"). The corresponding model class will be the capitalized, singular version "Message", and will be stored in app/models/message.rb. The controller (MessageController) will be stored in app/controllers/message_controller.rb. And the views (index.rhtml, new.rhtml, edit.rhtml, list.rhtml, etc.) can all be found in app/views/message/.

By following these simple guidelines, Rails can automatically find all the components in your application. This means, you do not have to create and maintain complex configuration files. Of course, you can explicitly define different relationships (for example, if you need to use a legacy database). But the whole idea behind Rails is simplicity. Or, as they say on their website DRY: Don't Repeat Yourself.

Just A Taste of Ruby

Rails is built using Ruby, a highly dynamic, completely object-oriented language that came out of Japan. Everything is a class. Even, integers (1, 9, -27) are Fixnum objects. You can call their methods (for example -27.abs or 9.size). Additionally, all classes are open. You can add your own methods. You can add methods dynamically at runtime (what some Ruby-pros refer to as Metaprogramming). You can even create a list of every object used in your application. Wrap each one in a proxy-class that overrides all of the object's public methods so it now prints "Whose your daddy?" to the standard output whenever called. I'm not sure why you'd want to...but you can.

You can redefine your ruby world until 2 + 2 = 5. You can make pigs equal to dogs. It's powerful, but a bit scary. Just remember what Spiderman said, "With great power comes great responsibility." Spiderman, right? Batman? Mary Lou Retton? Someone in tights, I'm pretty sure. The main point is, you can do these things,but you don't have to. No one's pointing a gun to your head. No one's forcing you to make crazy changes to Ruby's core classes. So just calm down. Take a deep breath. Everything's going to be OK.

As you will soon see, Ruby lends itself to a very idiomatic style of programming. You won't see a "for" loop anywhere in this tutorial. Ruby has its own, elegant way of iterating over groups. As a result, the programs may look very familiar, if you have a strong LISP background (show of hands, anyone? Hmm. That's what I thought). For the rest of us, it can seem somewhat bizarre at first. Never fear, it is quite easy to learn, and once you get the hang of it, very easy to use.

A full course in Ruby is well beyond the scope of this article. I will recommend some books and websites at the end. The following section; however, gives just a flavor of Ruby,specifically Ruby as used in Rails. It is not complete. Rather, I make the bold assumption that you have experience programming in another object-oriented language (java, Objective C, C++, and so on ), and I will only bring up those issues likely to trip you up. Or, at least, these are the things that tripped me up when I first started.

So, lets start with the source code. Ruby uses simple return characters to indicate the end of a command. You can separate commands with semicolons, if multiple commands are found on the same line. Additionally, you can freely break a command onto multiple lines, as long as the interpreter can tell from context that the command is continuing.

For example:

   print "This" + " is" + 
      " ok."


   print "This" + " is" + " also"  \
      + " ok"

In the second example, the backslash indicates that the command continues on the next line.

Rails also uses a standardized naming scheme. Classes begin with a capital letter, and each word in the name is capitalized with no spaces LikeThis. Variables and methods use lowercase letters, with underscores to separate words like_this. An object's instance variables begin with an at-sign @like_this. Instance variables are always private (you cannot access them directly outside the object). However, Ruby provides macros for automatically building accessor methods.

Object's method calls are similar to java: object.like_this(). There is one catch, however. As long as the arguments are clear, the parenthesis is optional. Therefore the following examples all call the like_this method of @my_object, passing in three arguments

@my_object.like_this(:one, :two, :three)
@my_object.like_this :one, :two, :three

Also note, in the above example, :one, :two and :three are symbols. Rails makes heavy use of symbols throughout. If you like, you can think of them as a specialized string. They're often used as keys in hashes.

And speaking of hashes, Rails uses two main collections for containing groups of objects: arrays and hashes. Arrays should seem familiar enough. Just remember, everything is an object (including arrays). Arrays have methods, and you can call these methods to do useful things. Hashes (sometimes called dictionaries) are key/value pairs. You create a hash using curly braces. This lets you look up the value later, using the key. Keys and values are separated as follows "key => value", and the key-value pairs are separated by commas. You access the value using square brackets, much like you would an array. But instead of holding the index, the brackets hold the key. It probably makes more sense when you look at it. The following example shows some basic hash manipulations.

   @hash = {:first_name => "John", :last_name => "Doe", :age => 23}
   @hash[:first_name]   produces "John"
   @hash[:last_name]   produces "Doe"
   @hash[:age]   produces 23

One last trick with hashes. Many of the methods in Rails accept hashes as arguments, often as a way of expressing options. If the hash is the last argument, the curly brackets are optional. So, all the following calls are equivalent.

   link_to("Send Email", {:action => "mail",  :id => @personnel})
   link_to("Send Email", :action => "mail",  :id => @personnel)
   link_to "Send Email", :action => "mail",  :id => @personnel

Ruby already allows you to format commands many different ways. Rails takes this one step forward. By aliasing methods, overloading operators and providing accessor methods, Rails often allows you to access one piece of information in a wide variety of ways. Don't let this throw you. For example, consider the following three commands.

Assuming @user is an instance variable containing an ActiveRecord object, and that the corresponding users table in the database has a "name" column, those commands should return the same value. There are some subtle differences, however. For example, calls the object's accessor method for the "name" attribute, while @user["name"] calls the object's read_attribute method with the "name" argument.

In general it's a good idea to use the accessors. You can tell, because the code is the shortest (always a good rule of thumb in Rails). I will follow this standard in the rest of this article. However, if you spot Rails code running around in the wild, you may see the other variations from time to time.

String literals come in two basic flavors: 'this' and "this". The first uses the literal directly. The second will process the string first. It is most often used as follows: "Welcome #{h(@name)}". #{...} is a block of Ruby code. The return value of the block will be converted into a string, then placed into the string literal.

OK, one last trick. Ruby uses a programming idiom called blocks. Blocks are sections of code either contained in curly brackets, or, for larger sections, marked by the 'do' and 'end' keywords. Blocks are typically used by methods similar to the way callback functions work. As the method executes, it calls the block one or more times. The method can even pass arguments to the block. Arguments are defined inside the beginning of the block, surrounded by bars |like_this|. Let's look at two examples.

   a = [1, 2, 3, 4]
   a.each {|value| print value}   produces 1234
   a = [[1,2],[3,4],[5,6]]
   a.each do |v1, v2|
print v1
print ","
print v2
print "--"
   end   produces 1,2--3,4--5,6--

In both cases, each method iterates over the array, passing one item at a time to the block. In the first example, the numbers are placed into the local variable "value" and are printed out. In the second case, the inner two-item arrays are passed out. The block automatically assigns the values from the inner array to the block variables v1 and v2. Again, everything is printed out,this time in a slightly more formatted way.

All right, enough of the preliminaries. Let's set up our system, and start building web applications.

Installing MySQL

Before we get started let me remind you, MySQL (like any server) potentially exposes your machine to attacks. I will lead you through the basic security, but remember I'm a programmer, not a system administrator. If you plan to leave MySQL running (and especially if you plan to actually use your machine as a server), please spend some quality time with the manuals. There's lots of information on securing MySQL both on the web and at your local bookstore.

Ok, first step: Download MySQL 5.0 from the MySQL website. Currently, you can go directly to Scroll way down the page (you probably want to search for "Mac OS X"). Eventually you will find several installer packages. Pick one,I downloaded the standard version.

The disk image I got contained four files. The first, mysql-standard-5.0.15-osx10.3-powerpc.pkg, installs the database. Install this. The second, MySQLStartupItem.pkg, will create a startup item for MySQL, causing it to launch automatically when your computer starts up. My system is old and slow enough as it is; I prefer to only start MySQL when I need it, then immediately stop it afterwards.

There is also a MySQL.prefPane. You can double click this to install a MySQL panel in your System Preferences. You might want this. It provides a convenient place to start and stop MySQL. It also sets MySQL to automatically launch on startup. However, I feel the control is rather sluggish, and it occasionally had trouble stopping MySQL on my system. So, for the purpose of this tutorial, we will run everything from the command line.

Finally, there's ReadMe.txt. Like the name says, read it., Particularly if you have trouble installing MySQL on your system. It also has useful information on using MySQL.

Dig around the MySQL site and you should find a few useful tools,in particular MySQL Administrator and MySQL Query Browser. I've used MySQL Administrator to create many databases, but it also has a few problems. Nothing you can't work around, but for now we'll stick to ye old trusty terminal.

Configuring MySQL

Let's launch MySQL. Open up Terminal and type the following:

sudo /usr/local/mysql/bin/mysqld_safe --user=mysql
ctrl -z

Type in your password. This will launch MySQL in the background. You should see the following:

Starting mysqld daemon with databases from /usr/local/mysql/data

You may have to hit return to get a new command line. If you are going to launch MySQL frequently, you might want to add the /usr/local/mysql/bin directory to your path, or alias this command.

We've got MySQL running, now lets hop inside and poke around.

   /usr/local/mysql/bin mysql -u root

Now you can control the database from the command line. First things first, let's look at the current users.

   use mysql
   select host, user from user

You should see both the root user and an anonymous user. First, let's delete the anonymous users. Of course, the response time may be different.

   delete from mysql.user where user = '';
   => Query OK, 2 rows affected (0.06 sec)

Then add a password to the root account.

   set password = password('password');
   => Query OK, 0 rows affected (0.07 sec)

This sets the password when connecting locally. If you need to remotely administer the database, you might want to set a password for remote connections. Since I'll always administer the database locally, I'll go one step further and remove the second root user. When we viewed the user table earlier, it showed two hosts. One was the local host. Use the other host name in the command below.

   delete from mysql.user where host='host_name' and user='root';
=> Query OK, 1 row affected (0.00 sec)

Now make sure you only have one user, using the local host, with an encrypted password.

select host, user, password from user;
+ -- -- -- --   + -- -- --+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --    +
| host          | user    | password                                                   |
+ -- -- -- --   + -- -- --+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --    +
| localhost     | root    |*B5B800A3935788309115348A78F7489B17AB90D                    |
+ -- -- -- --   + -- -- --+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --    +
1 row in set (0.00 sec)

Now test it by logging out and logging back in. This time you'll need to use the -p flag.

   /usr/local/mysql/bin/mysql -u root -p

MySQL will now prompt you for your password. The root user is now working and reasonably secure. We will use the root user to create our database and tables. However, we will want a special user for the web application to use.

First create the database.

   create database HoneyDo
   create database HoneyDo_test

Then create our user named hd_app. We will limit this user's rights to only allow basic database operations. While they can insert and delete data from the tables, they will not be able to change the tables themselves.

   grant select, insert, update, delete on HoneyDo.* to hd_app identified by 'user_password';
   grant select, insert, update, delete, index, alter, create, drop on HoneyDo_test.* to hd_app;

Note, we give the user more privileges on the test database. This is required for the testing suite. OK, we're done with the database for now. Go ahead and log out of mysql by typing '\q' and pressing enter.

Installing Rails

Tiger comes with Ruby already installed. You will need to install the XCode developer tools from your Tiger DVD.

First, set GCC to version 3.3. You can reset it to 4.0 once we are done.

sudo gcc_select 3.3

If you've upgraded XCode, there's an extra step. For some reason, XCode 2.2 moves the ruby header files to a different directory. The simplest solution is to just create simlinks for all the headers.

sudo ln -s /usr/lib/ruby/1.8/universal-darwin8.0/*.h /usr/lib/ruby/1.8/powerpc-darwin8.0/

For the RubyGems installation, you are going to need to download the package from the RubyGems website < > - release 0.9.0 as of this writing. Download the file, extract it, and install it in the shell:

cd ~/Desktop/rubygems-0.9.0  
sudo ruby setup.rb

Once successfully installed, we can install the Rails Framework itself. Type the following command:

sudo gem install rails

You will be asked to install many files and resolve dependencies. Say yes to all of them. What this does is use the RubyGems package manager to install the Rails framework.

Unfortunately, like the symlink fix above, there are a few other things that need to be taken care of with Ruby installation on Tiger. The RubyGems contains a fix for this that replaces Tiger's rbconfig.rb file with a working one. To make the update run the following commands:

sudo gem install fixrbconfig  
sudo fixrbconfig

You'll be asked to confirm this action. (Please say 'yes'!). The final part will be to install the Ruby SQLite interface. Once again, you can use the gem package manager:

sudo gem install sqlite3

You will be given several options to choose the first option, and you're on your way.

Generating The Application

All Right! Now we're ready to create our web application. On my computer, I keep all my Rails projects in a ~/rails_dev/ directory. To create our Honey Do List project, I would just type:

   mkdir ~/rails_dev
   cd ~/rails_dev
   rails HoneyDo
   cd HoneyDo

Unless otherwise stated, the rest of the commands will be run from the ~/rails_dev/HoneyDo directory. If you have any trouble with a command, make sure you're in the right directory.

   cd ~/rails_dev/HoneyDo

Getting To Know HoneyDo

Rails is a highly organized environment. It can avoid complex configuration files by making sure there's a place for everything, and keeping everything in it's place. It might seem draconic, but it works. Having edited poorly organized websites in the past (both my own and others) I quickly began to appreciate having this structure imposed upon me.

First let's look at the app folder. This folder will contain most of the source code for the project. It is further divided into controllers, models, views and helpers, not surprising for an MVC-based framework. The Views folder is further divided, one folder per controller, plus a Layouts folder for templates.

Next look at the config folder. While Rails tries to avoid configuration files when possible, it is not always possible. We will use both config/database.yml and config/routes.rb in this tutorial.

Both the lib and vendor folders can be used to hold shared code. Lib should hold your libraries, while vendor should hold third-party code.

The script folder holds Rails' utility scripts. This includes the generator scripts for automatically building models and controllers, as well as a script to launch the WEBrick server.

The test folder holds your testing suite. If you are like me, you will spend more time writing code in the test folder than in the app folder. Briefly, the fixtures subfolder contains any text data you create. The functional and unit folders contain test suites for the controllers and models respectively. Finally the mock folder contains any mock objects (for example, a mock mail server). This allows you to run tests without sending messages to the real object.

Finally there is the public folder. Static web pages go here. There are also built-in folders to hold style sheets, javascript and images. Rails will always look for a web page first in the public folder before running any application code. This is used by the cache system, if you cache a whole page, it will be converted into a static web page, and placed in the public folder. If you want to manually clear the cache, you can simply delete these pages.

There's more hidden away in the nooks and crannies. Take some time to walk through the folders and get a feel for the layout.

Setting Up Tables

First step, we need to edit the database.yml file located in the config folder. The final version should look like this:

# MySQL (default setup).  Versions 4.1 and 5.0 are recommended.
# Get the fast C bindings:
#   gem install mysql
#   (on OS X: gem install mysql -- --include=/usr/local/lib)
# And be sure to use new-style password hashing:
  adapter: mysql
  database: HoneyDo
  username: hd_app
  password: usr_password
  socket: /tmp/mysql.sock
  # Connect on a TCP socket.  If omitted, the adapter will connect on the
  # domain socket given by socket instead.
  #host: localhost
  #port: 3306
# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
  adapter: mysql
  database: HoneyDo_test
  username: hd_app
  password: usr_password
  socket: /tmp/mysql.sock
  adapter: mysql
  database: HoneyDo
  username: hd_app
  password: usr_password
  socket: /tmp/mysql.sock

You can safely delete the rest.

For the purpose of this tutorial, we are using the same database for both development and production. This will allow us to run our application in either debug or production mode, without building a second database. Testing, however, needs its own database, since its contents will be destroyed when running the test suite.

Now create a file containing the test sql.

drop table if exists items;
create table items(
   id    int    not null auto_increment,
   title   varchar(100)   not null,
   description   text    not null,
   primary key (id)

We can create this table by executing the following command:

   /usr/local/mysql/bin/mysql HoneyDo -u root -p < test.sql
   /usr/local/mysql/bin/mysql HoneyDo_test -u root -p < test.sql

Now generate the model and controller for this table.

ruby script/generate model Item
ruby script/generate controller Item

Ok, let's add one line of actual code, and we're ready to go. Edit app/controllers/item_controller.rb by adding the line scaffold :item as shown below.

class ItemController < ApplicationController
   scaffold :item

Now make sure everything is working OK. We'll run the tests automatically created by the generated script. We'll use rake to perform the tests.


By default, rake will run both the functional and unit tests. You should see a similar result line for each test.

   1 tests, 1 assertions, 0 failures, 0 errors

If you get any errors, something is wrong. Go over the instructions again and make sure you did not miss anything.

Testing is beyond the scope of this article. In an ideal world, you would create a thorough set of tests as you develop the application. Many developers write the tests before the code, using it as their specification for the actual code. Testing early and often will save you a lot of heartache later. Trust me.

If everything looks good, let's take this puppy out for a test drive. Open a new terminal window, and from the HoneyDo directory launch Rail's built-in server. You could run the server in the background, but it produces a lot of output. Additionally, anything you print from the application (e.g. using print or puts) will appear here. I often find it useful to watch the output while debugging. So I keep it running in its own window.

   ruby script/server

Now, point your browser to the following url http://localhost:3000. You should see the "Congratulations, you've put Ruby on Rails!" page. That means the server's working. Now try http://localhost:3000/item. Aha! Now you're seeing your default item list (currently empty). Click on the New item link, and it brings up an automatically generated form. Go ahead, add a few items to your database. Play around with the interface: look at the list, show specific items, edit them, and delete them. All the basic functions are already at your fingertips.

All this comes from the magic of scaffold. This command causes Rails to check the items table in the database, and dynamically creates pages based on its columns. Scaffold may not produce the most attractive pages. We will rewrite most of the default scaffold pages as we develop our application. But it does allow us to quickly make something functional. This is a key insight into Rails development, we quickly produce a basic application, then iteratively add improvements.

So, if these pages are dynamic, what happens if we add a new column to our table? Let's try. Log into the database as root:

   /usr/local/mysql/bin/mysql HoneyDo -u root -p

Then type the following sql command and exit.

   alter table items add date date after description;

Now go back to your browser and add a new item. Notice the nice, new combo boxes for selecting the date? Some days life is easy.

Building the Basic App

OK, time to put the toys away and build a real application. First, create a file named final.sql containing the following commands:

drop table if exists items;
create table items(
   id   int   not null auto_increment,
   title   varchar(100)    not null,
   description   text    not null,
   priority   int   not null,
   date   date   not null,
   user_id   int   not null,
   sender_id   int   not null,
   constraint fk_items_user foreign key (user_id) references users(id),
   constraint fk_items_sender foreign key (user_id) references users(id),
   primary key (id)
drop table if exists users;   
create table users (
   id   int    not null auto_increment,
   login    varchar(80)   default NULL,
   password    varchar(40)   default NULL,
   primary key  (id)

Now load this schema into both our development and our test databases:

   mysql -u root -p HoneyDo < final.sql
   mysql -u root -p HoneyDo_test < final.sql

First we need a login system. Fortunately, there's a basic login and authentication generator available as a ruby gem. Let's install the generator:

   sudo gem install login_generator

Now build the login and authentication system. This creates the framework to create new users, and to allow users to log in. Note: this only provides a basic login framework. It does not let you assign different privileges to different users. However, it is very easy to expand this system to include access control lists. You can find examples at But for now, we'll stick with the basics.

   ruby script/generate login Security
   Now modify app/controllers/application.rb as follows:
require_dependency 'login_system' 
class ApplicationController < ActionController::Base
    include LoginSystem
    model :user

We only want valid users to access our item controller. So modify app/controlles/item_controller.rb

class ItemController < ApplicationController
   before_filter :login_required
   scaffold :item

If someone is not logged in, they should only have access to the login and signup actions. Let's lock down everything else. In the app/controllers/security_controller.rb add the following line:

class SecurityController < ApplicationController
  before_filter :login_required, :except => [:login, :signup]
  layout  'scaffold'

One more thing, open up the app/models/user.rb file. You need to change the salt setting from the default "change-me" value. If you are feeling particularly paranoid, you could create a random hex-only salt value for each user, which could then be appended to the password value and saved in the database.--But, I leave that as an exercise for the user.

OK, crank up the rails server and try it out. Going to any of the localhost:3000/item pages should automatically redirect you to the login screen. To create a new user, go to localhost:3000/security/signup. Once you sign up, try going to the item pages again.

If you were observant, you probably noticed that after signing in, you were sent to a pretty worthless welcome page. We could edit the welcome page. But instead, let's just send everyone directly to the item list. While we're at it, we'll also set the security index page to the list. In app/controllers/security_controller.rb change the empty welcome function, and add the following index function.

def welcome
  redirect_to (:controller => 'item', :action => 'list')
def index
  redirect_to (:controller => 'item', :action => 'list')

And while we're rerouting things, let's take a look at the basic routing definitions. These are found in config/routes.rb. If you open that file, you will see several lines starting with map.connect.... These determine how URLs are routed through your application. It shows that the following URL localhost:3000/item/show/1 would be routed to the show action of the ItemController, passing in a parameter id=1. The last two elements are optional. For example, localhost:3000/item/list fires ItemController's list action with no id. And localhost:3000/item fires its index action (which, in our case, defaults back to the list action).

While we're here, let's make sure that localhost:3000 requests go somewhere useful. Find the line that reads:

   # map.connect '', :controller => "welcome"

and change it to:

   map.connect '', :controller => 'item', :action => 'list'

Save and close this file. Now, delete the public/index.html file. From now on pointing your browser to localhost:3000 will send you directly to your ToDo list, or at least ask you to log in.

One last note about security. Once you log in, you will remain logged in until your session expires. There are two ways you can force your session to expire (which can be very useful when manually testing). First, close down your browser. You have to actually quit the application, not just shut the window. Second, navigate to localhost:3000/security/logout. We'll add a convenient link to the logout screen in the next section.

Iterative Improvements

So what have we done so far? We can sign-up and login to our site. We have a basic ToDo list. We can add items and delete view and edit the items in our list. But right now, all users are viewing copies of the same list. We want to sort our ToDo list by item priority, maybe make it look a bit nicer. We also need to improve the list creation/editing interface.

First let's add a template for all web pages. For the purpose of this article, we'll make it stupidly simple. And we'll use the same template for all pages.

Create a new file named template.rhtml in the app/views/layouts folder. Edit the file as follows:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   <%= stylesheet_link_tag 'scaffold' %>
   <title><%= controller.action_name%></title>
   <%= "<p style='color: green'>#{h(@flash[:notice])}</p>" if @flash[:notice] %>
   <%= @content_for_layout %>
   <div align="center">
      <%= %>
      | <%= link_to_unless_current 'Add New Item', :controller =>
         'Item', :action => 'new' %>
      | <%= link_to_unless_current 'Show ToDo List', :controller =>
         'Item', :action => 'list' %>
      | <%= link_to 'logout', :controller => 'Security',
         :action => 'logout'%>

RHTML files are simply HTML files that can include ruby scripts. These will look quite similar to JSP scripts. Anything between "<%" and "%>" will be executed as a ruby script. "<%=" and "%>" scripts will be executed and will have the results placed into the HTML document.

Rails also includes, a number of helper functions. Here link_to() and link_to_unless_current() are helper functions. They automatically create URLs that follow the current routing rules for the given actions. Additionally, link_to_unless_current() only creates a link when you are not viewing the linked-to page.

The flash hash is used to pass messages between objects. Here, it looks for any :notice objects (used by both scaffold and security). You can see how the messages are sent by looking at the security model (app/model/security.rb).

Play around with the application a bit. See how you like the new look. If you feel adventurous, change the HTML around some. Personalize it.

Now we need to tell Rails to use this template. Add the following line to both app/controllers/item_controller.rb and app/controllers/security_controller.rb

class ItemController < ApplicationController
   before_filter :login_required
   scaffold :item
   layout 'template'

Now we need to tell Rails to use this template. Add the following line to both app/controllers/item_controller.rb and app/controllers/security_controller.rb

class ItemController < ApplicationController
   before_filter :login_required
   scaffold :item
   layout 'template'

Now we need to connect the item's foreign keys. In each item, both the user_id and the sender_id refer to valid users. In both cases, this is a one-to-may relationship. Each user can have any number of ToDo items assigned to him. Likewise, each user can send out any number of ToDo items. But each item can only have one user and one sender.

First, we will tell Rails that the items belong to both the users and the senders. To do this, modify the item model (app/models/item.rb). Add the following lines:

class Item < ActiveRecord::Base
   belongs_to :user
   belongs_to :sender, :class_name => 'User', :foreign_key => 'sender_id'

The first belongs_to() command is the typical form. However, since both foreign keys refer to the same model, we need to use a more verbose form for the second command. Here we are saying that the model's sender attribute is actually a User object, using the sender_id key from the database.

Not surprisingly, we need to call the has_many method in the user model (app/models/user.rb).

require 'digest/sha1'
# this model expects a certain database layout and it's based on the name/login pattern. 
class User < ActiveRecord::Base
  # Please change the salt to something else, 
  # Every application should use a different one 
  @@salt = 'change-me'
  cattr_accessor :salt
   has_many :todo_items, :class_name => 'Item', :foreign_key => 'user_id',
      :order => 'priority DESC, date'
   has_many :sent_items, :class_name => 'Item', :foreign_key => 'sender_id',
      :order => 'date'

Here both attributes are defined using the verbose form. Additionally, I've added the order option, which takes an SQL fragment. This will allow Rails to automatically order these items for us.

The last few changes should have had no visible effect on the application. Now it's time to improve the appearance of the ToDo list. First, let's override the scaffolding method in the item controller (app/controllers/item_controller.rb). Add the following function to the class definition:

   def list
      @user = @request.session[:user]
      @item_list = @user.todo_items
      @name = @user.login.capitalize
      @pages, @items = paginate(:item, :order_by => 'priority DESC, date', 
         :conditions => ['user_id = ?',])

This creates three variables that we will use in the page itself. @name is the capitalized version of the user's login name. @pages will be used to create the pagination links, and @items contains an array of ToDo items. The paginate command will, by default, only allow ten items per page. If we did not want the pagination, we could have just used @user.todo_items, which would automatically return a properly-ordered list of all items for the given user.

Now let's create a helper function for the item's views. Open app/helpers/item_helper.rb and edit it as follows:

module ItemHelper
   def priority_name(index)
      case index
      when 0
         "<font color='green'>Lowest</font>"
      when 1
         "<font color='green'>Low</font>"
      when 2
         "<font color='yellow'>Medium</font>"
      when 3
         "<font color='red'>High</font>"
      when 4
         "<font color='red'>Urgent!</font>"
         "<font color='red'>Undefined</font>"

Finally create two RHTML files in the app/views/item folder: list.rhtml and _row.rhtml.

<h1><%= h(@name) %>'s HoneyDo List:</h1>
<table border="1" cellspacing="0px" cellpadding="5px" align="center">   
   <tr bgcolor="cc9966">
   <%= render :partial => 'row', :collection => @items %>
<p><%= @item_list.length %> Active Items 
<%= '<pages: ' if @pages.length > 1 %>
<%= pagination_links @pages %>
<%= ' >' if @pages.length > 1%></p>
<% if (row_counter % 2) == 0 %>
   <tr bgcolor="#ccffff">
<% else %>
   <tr bgcolor="#ffff99">
<% end %>
   <td><%=h(truncate(row.description, 30))%></td>
      <%= link_to('Show', :action => 'show', :id => %> |
      <%= link_to('Edit', :action => 'edit', :id => %> |
      <%= link_to('Delete', {:action => 'destroy', :id =>}, 
      {:confirm => "Are you sure you want to delete #{row.title}"}) %>

The underscore at the front of _row.rhtml indicates that it is a partial. It cannot be displayed by itself. Rather, it is used by the list.rhtml file. The render(:partial .... :collection ...) method uses the _row.rhtml file to render each item in the @items list.

Note that before we display any text stored in the database we first escape it using the h() function. This is a standard security measure. I wish we lived in a world where we could trust our users. Unfortunately, we're not quite there yet.

The h() helper function prevents people from including malicious HTML code in their ToDo items. The h() method escapes all special HTML characters, replacing greater than and less than characters with > and <. If you want to let your users use some (relatively safe) HTML code, you can use the sanitize() function instead. Rails also includes, support for a variety of text formatters. The results of the priority_name() helper function does not need to be escaped, since we entered its HTML code.

We should similarly override item's show() method. Besides wanting to improve the layout, it will currently display the information without escaping it. However, I will leave that as an exercise for the reader.

We still need a better interface for adding and editing items. First add the following four methods to the item's controller (app/controllers/item_controller.rb).

   def new
      @item =
      @users = User.find(:all, :order => 'login')
   def edit
      @item = Item.find(@params['id'])
      @users = User.find(:all, :order => 'login')
   def create
      user = @request.session[:user]
      user_id = @params['item'].delete('user_id')
   @item =['item'])
   @item.sender = user
   @item.user = User.find(user_id) =
   flash[:notice] = 
      "#{@item.title} successfully added to #{@item.user.login}'s ToDo list!"
      redirect_to :action => 'list'
      @users = User.find(:all, :order => 'login')
      render_action 'new'
   def save
      user = @request.session[:user]
      item_hash = @params['item']
      user_id = item_hash.delete('user_id')
      @item = Item.find(item_hash['id'])
      # don't update the sender!
   @item.user = User.find(user_id)
   # don't update the time!
      if @item.update_attributes(item_hash)
      flash[:notice] = "#{@item.title} successfully updated!"
   redirect_to :action => 'list'
      @users = User.find(:all, :order => 'login')
      render_action 'edit'

And create both the new and edit templates (app/views/item/new.rhtml and app/views/item/edit.rhtml respectively)

<%= error_messages_for(:item, :id => 'ErrorExplanation') %>
<%= form_tag :action => 'create' %>
   <table cellpadding="5px">
         <td><%=text_field :item, :title %></td>
            <%=select :item, :priority, 
            [['Lowest', 0], ['Low', 1], 
            ['Medium', 2], ['High', 3], 
            ['Urgent!', 4]]%>
         <td><b>Send To User:</b></td>
            <%= collection_select(:item, :user_id, 
               @users, :id, :login)%>
         <td colspan="2">
            <%=text_area :item, :description, :cols => '60',
               :rows => '20' %></p>
            <%= submit_tag "Create ToDo Item"%>
<%= end_form_tag %>
<%= error_messages_for(:item, :id => 'ErrorExplanation') %>
<%= form_tag :action => 'save' %>
   <%=hidden_field :item, :id %>
   <table cellpadding="5px">
         <td><%=text_field :item, :title %></td>
            <%=select :item, :priority, [['Lowest', 0],['Low', 1],
               ['Medium', 2], ['High', 3], ['Urgent!', 4]]%>
         <td><b>Send To User:</b></td>
            <%= collection_select(:item, :user_id, 
               @users, :id, :login)%>
         <td colspan="2">
            <%=text_area :item, :description, :cols => '60',
               :rows => '20' %></p>
            <%= submit_tag "Save Changes"%>
<%= end_form_tag %>

Notice, these templates are all quite similar. There are a few subtle differences, but you could probably move much of the code into a common partial template. Again, I'll leave that as homework. The create() and save() functions also are quite similar, but they're small enough and different enough to ignore.

All right, take a deep breath and look at what we've done. Play around with the interface. Create multiple users. Notice how easily you can assign ToDo tasks to other users. Try editing tasks, and changing the user (thus sending it off to be someone else's responsibility). Create a lot of tasks for yourself. Notice how the pagination automatically kicks in after you create the eleventh task.

One more step, and we're done. Let's add validation. Open the item model (app/modles/item.rb). Edit it as follows:

class Item < ActiveRecord::Base
   belongs_to :user
   belongs_to :sender, :class_name => 'User', :foreign_key => 'sender_id'
   def validate
      unless (0...5).include?(priority)
         errors.add(:priority, 'Is invalid. Must be from 0 to 4')
   validates_format_of :title, :with => /\S/, 
      :message => 'Title cannot be blank!'
   validates_presence_of :date, :message => 'Date missing!'
   validates_presence_of :user, :message => 'User missing!'
   validates_presence_of :sender, :message => 'Sender missing!'
   # Description is optional--not validated

First, we have added a custom validate() function. This function simply checks the priority and makes sure it is greater than or equal to 0 and less than 5. Then we use several built-in validation functions. validates_format_of() tests the title against a regular expression. Here, we're simply making sure the title has at least one non-whitespace character. Next, validates_presence_of() makes sure both the date and user attributes have non-nil values. (see figure 1.)

I haven't talked about testing in a while. Unfortunately, there's a reason for this. When we generated the login system, the generator created a decent suite of tests for us. However, as we've moved to the 1.0 release of Rails, some things have changed. One important change is to the testing system. There have been several performance-related improvements, which can (unfortunately) break older testing suites, like those automatically created by the generator.

For simplicity's sake, I'm going to use the old-style testing. To do this, we need to add the following lines to the beginning of all test case classes (or modify test/test_helper.rb).

self.use_transactional_fixtures = false
self.use_instantiated_fixtures = true

As of writing this, most of the documentation at still describes the older style tests. Mike Clark has a thorough description of these changes on his blog at

Figure 1.

Again, writing tests is beyond the scope of this article. I have, however, produced a set of reasonably thorough tests for this project. These can be found in the source code in the following seven files: test/test_helper.rb, test/unit/item_test.rb, test/unit/user_test.rb, test/functional/item_controller_test.rb, test/functional/security_controller_test.rb, test/fixtures/items.yml, and test/fixtures/users.yml (whew!). The tests are organized into two test suites. Unit tests are used for checking the models. Functional tests are for checking the controller and (to a lesser extent) the view.

Note: The item controller tests do some checks on the HTML. If you make changes to the output format, you may need to change these tests. For simplicity, they are all grouped into the assert_html_check() function in test/test_helper.rb. You can run all the tests by simply typing rake. The following two commands will let you run the unit tests and the functional tests separately: rake test_units, rake test_functional.

Problems with Rails

Don't get me wrong. I really enjoy working with Rails. However, all is not sunlight and roses. One of the biggest problems is that Rails is still an emerging technology. While on the one hand it's under active development, it is also a moving target. More importantly, you may have to search for a host who supports Rails (though has an ever-growing list). Rails is only about 18 months old. If it manages to live up to even half its current buzz, this problem will go away.

The second problem is more surprising. Rails stores session information either in files (by default) or in a database table. While Rails automates away so many fiddling issues in web development, it doesn't do anything about this session data. If left unchecked, these files (or tables) will grow until they crash your server. This is a bad thing.

This problem can easily be solved, but it requires you to schedule chron jobs on the hosting computer. This is probably not a problem on any host who actively supports Rails, but it seems like something the framework should manage.

Ideas For The Future

We've got a functional web application, but there's definitely room for improvement, and one rather dangerous bug still lurking around.

Let's start with the bug. We're still using scaffolding when you display a single item. First, we should be able to improve the appearance of these pages. More importantly, scaffolding does not escape the text before displaying it. If a user entered dangerous html code in the item's description, scaffolding will innocently display it, possibly with disastrous results.

Bottom line, scaffolding is useful for rapid development, but I'd remove it before taking any system live.

There's a second, lesser bug. Right now, any valid user can view any item (even items that belong to other users), as long as they know (or can guess) the item's id. You can try this at home by directing your browser to the following URL :


Again, if we implement the show() action, we should verify that the logged-in user and the item's user match.

Ok, enough of that. What cool features can we add?

One obvious improvement is adding permissions. A simple division would be to split users into "administrators" and "users", where administrators could access, edit, and delete other user accounts. While we're at it, there's currently no way for users to change their password.

Second, currently when you create a new item, you can send that item to any valid user (and all the users are displayed in a single, ungainly list). This is fine when you have a half-dozen users, but just won't work if you have thousands. One solution is to create an invite-only system, where each user can invite other users, and only invited users can send them ToDo items. For a more business-oriented application, you could organize the users into a hierarchy, where only your immediate supervisor can send you ToDo items. Rails' ActiveRecord can easily handle all these. Trees are a little more complex than the simple has-one or has-many relationships, but Rails takes most of the pain out of it. We don't have any way to view the items a user has sent out. That would be nice. Items themselves could be improved. We could add due dates. We could add a status and completed flags. We could group them into categories. All of this could be implemented easily with the techniques already presented in this article.

What about automatically sending email messages to the users? Many sites send an e-mail message that you must respond to when you first sign up. We could do something like that. Ruby also supports both reading and generating RSS feeds. We could display new ToDo items to a user's personalized RSS feed (though that would let anyone who could guess the URL read their ToDo items).

Last, but not least, there's Ajax. I'm sure you've heard about Ajax.It has gotten even more buzz than Rails. In a nutshell, Ajax uses fancy javascript tricks to produce web pages that respond more quickly to user actions. This means web pages act more like traditional desktop applications. A ToDo list has prime opportunities for Ajax goodness. However, as anyone who works with client-side web technologies knows, it's only good if the browser supports it. This can make writing and testing raw Ajax functions a real pain.

Fortunately, Rails comes complete with a full set of Ajax helper functions. These functions create tested Ajax code that plays nice with all the Rails components. With these, implementing an Ajax interface is almost as easy as implementing a mundane, HTML interface.


First I'd recommend checking out the Ruby on Rails website ( It's your one-stop portal into a wealth of information about both Ruby and Rails. However, I'd also like to highlight the following resources:


  • Agile Web Development with Rails

  • Programming Ruby: The Pragmatic Programmers' Guide, Second Edition

  • Ruby In A Nutshell

Learning Ruby

Other Rails Tutorials:



  • Riding Rails: feed://

  • Ruby Code and Style: feed://


    Rich Warren lives in Honolulu, Hawaii with his wife Mika and daughter Haruko. He is a freelance writer, programmer and part-time Graduate student at the University of Hawaii in Manoa. When not playing on the beach with his daughter, he can be found writing, studying, doing research or building web applications--all on his PowerBook. You can reach Rich at


    Community Search:
    MacTech Search:

    Software Updates via MacUpdate

    FileMaker Pro 19.4.2 - Quickly build cus...
    FileMaker Pro is the tool you use to create a custom app. You also use FileMaker Pro to access your app on a computer. Start by importing data from a spreadsheet or using a built-in Starter app to... Read more
    Adobe Illustrator 26.0.3 - Professional...
    You can download Adobe Illustrator for Mac as a part of Creative Cloud for only $20.99/month. Adobe Illustrator for Mac is the vector graphics classics in the design industry. It is a digital... Read more
    WhatRoute 2.4.9 - Geographically trace o...
    WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the router... Read more
    Notion 2.0.20 - A unified workspace for...
    Notion is the unified workspace for modern teams. Notion Features: Integration with Slack Documents Wikis Tasks Release notes were unavailable when this listing was updated. Download Now]]> Read more
    Monterey Cache Cleaner 17.0.2 - Clear ca...
    Monterey Cache Cleaner is an award-winning general-purpose tool for macOS X. MCC makes system maintenance simple with an easy point-and-click interface to many macOS X functions. Novice and expert... Read more
    Firetask Pro 4.6.8 - Innovative task man...
    Firetask Pro represents the next generation of easy-to-use, project-oriented task management apps. By combining David Allen's powerful Getting Things Done (GTD®) approach with classical task... Read more
    Smultron 13.0.4 - Easy-to-use, powerful...
    Smultron 13 is the text editor for all of us. Smultron is powerful and confident without being complicated. Its elegance and simplicity helps everyone being creative and to write and edit all sorts... Read more
    Box Sync 4.0.8057 - Online synchronizati...
    Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
    Audio Hijack 3.8.10 - Record and enhance...
    Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
    Direct Mail 6.0.1 - Create and send grea...
    Direct Mail is an easy-to-use, fully-featured email marketing app purpose-built for macOS. Create, send, and track great looking email campaigns that get results. Start your newsletter by selecting... Read more

    Latest Forum Discussions

    See All

    SwitchArcade Round-Up: ‘Pokemon Legends:...
    Hello gentle readers, and welcome to the SwitchArcade Round-Up for January 28th, 2022. We’ve got a bunch of new releases to look at today, with a few big hitters, a few mid-level diversions, and a healthy supply of compost. Since it’s Friday, we... | Read more »
    Phantom Blade: Executioners, S-Game...
    S-Game has kicked off its first Closed Beta Test for Phantom Blade: Executioners, inviting a selected few to get first dibs on the upcoming KungFuPunk action RPG on mobile. The CBT officially begins this January 28th, and beta testers will receive... | Read more »
    ‘Infinite Galaxy’ First Anniversary: Cel...
    Cultivating a new generation of valiant commanders across 240 countries worldwide, Infinite Galaxy has quenched players’ thirst to explore the vastness of space – and there are only more intergalactic adventures to embark on from here on out. Camel... | Read more »
    War and Order: How to brave the cold in...
    War and Order's 6th-anniversary celebrations are underway, and all in good time too - this season not only brings about fabulous festivities, but it also lets players experience the harsh winter in an entirely new way. [Read more] | Read more »
    ‘Hidden Folks+’ Is This Week’s New Apple...
    The original Hidden Folks from Adriaan de Jongh is an excellent hidden objects game featuring hand drawn visuals. It is an absolute joy to play, and it has now released on Apple Arcade in the form of Hidden Folks+ () as an App Store great. If you’... | Read more »
    Mini Metro’s First Big Update of 2022 Ad...
    Last year saw great updates for Dinosaur Polo Club’s Mini Metro ($3.99) which is also available on Apple Arcade as an App Store Great. | Read more »
    SwitchArcade Round-Up: ‘Gunvolt Chronicl...
    Hello gentle readers, and welcome to the SwitchArcade Round-Up for January 27th, 2022. In today’s article, we’ve got a whole bunch of new releases to check out. It’s a dangerous one for the wallet today, as there are several interesting games to... | Read more »
    SIEGE: Apocalypse lets you engage in mil...
    Launching today to the delight of military enthusiasts across the globe, SIEGE: Apocalypse is a new 1v1 military battler from KIXEYE that's set in the early days of the War Commander universe. Players need to collect and upgrade unit cards to build... | Read more »
    ‘SIEGE: Apocalypse’, KIXEYE’s Military-T...
    Military fans across the globe now have more reasons to dive into the War Commander universe as KIXEYE launches SIEGE: Apocalypse on both iOS and Android devices today. The 1v1 military battler pits two players against each other in intense real-... | Read more »
    ‘Yu-Gi-Oh! Master Duel’ Is Rolling Out N...
    Following its launch on PC and all consoles last week, Yu-Gi-Oh! Master Duel has finally released on mobile platforms. Since launch, the game has exploded on multiple platforms with it having over 260k concurrent players on Steam. It has full... | Read more »

    Price Scanner via

    Apple has clearance 2020 13″ MacBook Airs ava...
    Apple has clearance, Certified Refurbished, 2020 13″ Intel-based MacBook Airs in stock today starting at only $719 and up to $370 off original MSRP. Each MacBook features a new outer case, comes with... Read more
    The cheapest iPhones for sale today at Apple...
    Apple has restocked Apple Certified Refurbished iPhone 8 models starting at only $359. Each refurbished iPhone comes with a fresh external case, standard Apple 1-year warranty, and free shipping.... Read more
    14″ MacBook Pro with Apple M1 Max CPU now in...
    Looking for a new 14″ MacBook Pro with an Apple M1 Max CPU? Stock is finally trickling into Apple resellers. B&H has Silver 14″ M1 Max MacBook Pros in stock today for $2899 including free 1-2 day... Read more
    14″ MacBook Pros with Apple M1 Pro CPUs are i...
    Amazon is reporting stock of 14″ MacBook Pros with M1 Pro CPUs today with a $50 discount. Shipping is free, and delivery is available by February 1st for most configurations. Be sure to make your... Read more
    Apple has restocked 13″ M1 MacBook Pros for $...
    Apple has restocked a full line of 13″ M1 MacBook Pros available Certified Refurbished, starting at only $1099 and up to $230 off original MSRP. These are the cheapest M1 MacBook Pros for sale today... Read more
    Apple’s AirPods Max headphones are on sale fo...
    Amazon has Silver, Blue, and Space Gray Apple AirPods Max headphones on sale today for $100 off MSRP. Shipping is free, and all models are in stock today. Their price is the lowest currently... Read more
    Open a new line of service at Verizon and get...
    Verizon is giving away 64GB Apple iPhone 12 minis or your choice of an iPhone 11 to customers who choose one of these phones and open a new line of service. Offer is available online only, and no... Read more
    Open-box 13″ M1 MacBook Airs now available st...
    QuickShip Electronics has open-box return 13″ M1 MacBook Airs in stock and on sale for $200-$400 off MSRP on their eBay store right now with free express delivery. According to QuickShip, “The item... Read more
    Verizon’s 2022 iPad promo: $100-$310 off any...
    Verizon has cellular-capable iPads on sale for $100-$310 off MSRP when purchased with an Unlimited service plan. Sale price is applied to your account monthly over a 24 or 30 month period, depending... Read more
    Sunday Sale: Apple AirPods are on sale for up...
    Amazon has Apple AirPods on sale for $10-$100 off MSRP today, depending on the model. All are in stock today with free delivery: – AirPods Max headphones (Blue): $449 $100 off MSRP – AirPods Max... Read more

    Jobs Board

    Registered Nurse (RN) Employee Health PSJH -...
    …is calling for a Registered Nurse (RN) Employee Health PSJH to our location in Apple Valley, CA.** We are seeking a Registered Nurse (RN) Employee Health PSJH to be Read more
    Systems Administrator - Pearson (United State...
    …and troubleshoot Windows operating systems (workstation and server), laptop computers, Apple iPads, Chromebooks and printers** + **Administer and troubleshoot all Read more
    IT Assistant Level 1- IT Desktop Support Anal...
    …providing tier-1 or better IT help desk support in a large Windows and Apple environment * Experience using IT Service Desk Management Software * Knowledge of IT Read more
    Human Resources Business Partner PSJH - Provi...
    …**is calling a** **Human Resources Business Partner, PSJH** **to our location in Apple Valley, CA.** **Applicants that meet qualifications will receive a text with Read more
    Manager Community Health Investment Programs...
    …is calling a Manager Community Health Investment Programs PSJH to our location in Apple Valley, CA.** **Qualified candidates will be invited to do a self-paced video Read more
    All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.