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

    coconutBattery 3.9.14 - Displays info ab...
    With coconutBattery you're always aware of your current battery health. It shows you live information about your battery such as how often it was charged and how is the current maximum capacity in... Read more
    Keynote 13.2 - Apple's presentation...
    Easily create gorgeous presentations with the all-new Keynote, featuring powerful yet easy-to-use tools and dazzling effects that will make you a very hard act to follow. The Theme Chooser lets you... Read more
    Apple Pages 13.2 - Apple's word pro...
    Apple Pages is a powerful word processor that gives you everything you need to create documents that look beautiful. And read beautifully. It lets you work seamlessly between Mac and iOS devices, and... Read more
    Numbers 13.2 - Apple's spreadsheet...
    With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
    Ableton Live 11.3.11 - Record music usin...
    Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
    Affinity Photo 2.2.0 - Digital editing f...
    Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
    SpamSieve 3.0 - Robust spam filter for m...
    SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
    WhatsApp 2.2338.12 - Desktop client for...
    WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
    Fantastical 3.8.2 - Create calendar even...
    Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event details... Read more
    iShowU Instant 1.4.14 - Full-featured sc...
    iShowU Instant gives you real-time screen recording like you've never seen before! It is the fastest, most feature-filled real-time screen capture tool from shinywhitebox yet. All of the features you... Read more

    Latest Forum Discussions

    See All

    The iPhone 15 Episode – The TouchArcade...
    After a 3 week hiatus The TouchArcade Show returns with another action-packed episode! Well, maybe not so much “action-packed" as it is “packed with talk about the iPhone 15 Pro". Eli, being in a time zone 3 hours ahead of me, as well as being smart... | Read more »
    TouchArcade Game of the Week: ‘DERE Veng...
    Developer Appsir Games have been putting out genre-defying titles on mobile (and other platforms) for a number of years now, and this week marks the release of their magnum opus DERE Vengeance which has been many years in the making. In fact, if the... | Read more »
    SwitchArcade Round-Up: Reviews Featuring...
    Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 22nd, 2023. I’ve had a good night’s sleep, and though my body aches down to the last bit of sinew and meat, I’m at least thinking straight again. We’ve got a lot to look at... | Read more »
    TGS 2023: Level-5 Celebrates 25 Years Wi...
    Back when I first started covering the Tokyo Game Show for TouchArcade, prolific RPG producer Level-5 could always be counted on for a fairly big booth with a blend of mobile and console games on offer. At recent shows, the company’s presence has... | Read more »
    TGS 2023: ‘Final Fantasy’ & ‘Dragon...
    Square Enix usually has one of the bigger, more attention-grabbing booths at the Tokyo Game Show, and this year was no different in that sense. The line-ups to play pretty much anything there were among the lengthiest of the show, and there were... | Read more »
    Valve Says To Not Expect a Faster Steam...
    With the big 20% off discount for the Steam Deck available to celebrate Steam’s 20th anniversary, Valve had a good presence at TGS 2023 with interviews and more. | Read more »
    ‘Honkai Impact 3rd Part 2’ Revealed at T...
    At TGS 2023, HoYoverse had a big presence with new trailers for the usual suspects, but I didn’t expect a big announcement for Honkai Impact 3rd (Free). | Read more »
    ‘Junkworld’ Is Out Now As This Week’s Ne...
    Epic post-apocalyptic tower-defense experience Junkworld () from Ironhide Games is out now on Apple Arcade worldwide. We’ve been covering it for a while now, and even through its soft launches before, but it has returned as an Apple Arcade... | Read more »
    Motorsport legends NASCAR announce an up...
    NASCAR often gets a bad reputation outside of America, but there is a certain charm to it with its close side-by-side action and its focus on pure speed, but it never managed to really massively break out internationally. Now, there's a chance... | Read more »
    Skullgirls Mobile Version 6.0 Update Rel...
    I’ve been covering Marie’s upcoming release from Hidden Variable in Skullgirls Mobile (Free) for a while now across the announcement, gameplay | Read more »

    Price Scanner via

    New low price: 13″ M2 MacBook Pro for $1049,...
    Amazon has the Space Gray 13″ MacBook Pro with an Apple M2 CPU and 256GB of storage in stock and on sale today for $250 off MSRP. Their price is the lowest we’ve seen for this configuration from any... Read more
    Apple AirPods 2 with USB-C now in stock and o...
    Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
    New low prices: Apple’s 15″ M2 MacBook Airs w...
    Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
    New low price: Clearance 16″ Apple MacBook Pr...
    B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
    Switch to Red Pocket Mobile and get a new iPh...
    Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
    Apple continues to offer a $350 discount on 2...
    Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
    Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
    Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
    Closeout Sale: Apple Watch Ultra with Green A...
    Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
    Use this promo code at Verizon to take $150 o...
    Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
    New low price: Apple’s 10th generation iPads...
    B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more

    Jobs Board

    Optometrist- *Apple* Valley, CA- Target Opt...
    Optometrist- Apple Valley, CA- Target Optical Date: Sep 23, 2023 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 796045 At Target Read more
    Senior *Apple* iOS CNO Developer (Onsite) -...
    …Offense and Defense Experts (CODEX) is in need of smart, motivated and self-driven Apple iOS CNO Developers to join our team to solve real-time cyber challenges. Read more
    *Apple* Systems Administrator - JAMF - Activ...
    …**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
    Child Care Teacher - Glenda Drive/ *Apple* V...
    Child Care Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Share on Facebook Apply Read more
    Machine Operator 4 - *Apple* 2nd Shift - Bon...
    Machine Operator 4 - Apple 2nd ShiftApply now " Apply now + Start apply with LinkedIn + Apply Now Start + Please wait Date:Sep 22, 2023 Location: Swedesboro, NJ, US, Read more
    All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.