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

    Duplicate Annihilator 5.7.5 - Find and d...
    Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator... Read more
    BusyContacts 1.0.2 - Fast, efficient con...
    BusyContacts is a contact manager for OS X that makes creating, finding, and managing contacts faster and more efficient. It brings to contact management the same power, flexibility, and sharing... Read more
    Capture One Pro - RAW workflow...
    Capture One Pro 8 is a professional RAW converter offering you ultimate image quality with accurate colors and incredible detail from more than 300 high-end cameras -- straight out of the box. It... Read more
    Backblaze - Online backup serv...
    Backblaze is an online backup service designed from the ground-up for the Mac.With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more
    Little Snitch 3.5.2 - Alerts you about o...
    Little Snitch gives you control over your private outgoing data. Track background activity As soon as your computer connects to the Internet, applications often have permission to send any... Read more
    Monolingual 1.6.4 - Remove unwanted OS X...
    Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. If you use your computer in only one (human) language, you... Read more
    CleanApp 5.0 - Application deinstaller a...
    CleanApp is an application deinstaller and archiver.... Your hard drive gets fuller day by day, but do you know why? CleanApp 5 provides you with insights how to reclaim disk space. There are... Read more
    Fantastical 2.0 - Create calendar events...
    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
    Cocktail 8.2 - General maintenance and o...
    Cocktail is a general purpose utility for OS X that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
    Direct Mail 4.0.4 - Create and send grea...
    Direct Mail is an easy-to-use, fully-featured email marketing app purpose-built for OS X. It lets you create and send great looking email campaigns. Start your newsletter by selecting from a gallery... Read more

    These are All the Apple Watch Apps and G...
    The Apple Watch is less than a month from hitting store shelves, and once you get your hands on it you're probably going to want some apps and games to install. Fear not! We've compiled a list of all the Apple Watch apps and games we've been able to... | Read more »
    Appy to Have Known You - Lee Hamlet Look...
    Being at 148Apps these past 2 years has been an awesome experience that has taught me a great deal, and working with such a great team has been a privilege. Thank you to Rob Rich, and to both Rob LeFebvre and Jeff Scott before him, for helping me... | Read more »
    Hands-On With Allstar Heroes - A Promisi...
    Let’s get this out of the way quickly. Allstar Heroes looks a lot like a certain other recent action RPG release, but it turns out that while it’s not yet available here, Allstar Heroes has been around for much longer than that other title. Now that... | Read more »
    Macho Man and Steve Austin Join the Rank...
    WWE Immortals, by Warner Bros. Interactive Entertainment and WWE, has gotten a superstar update. You'll now have access to Macho Man Randy Savage and Steve Austin. Both characters have two different versions: Macho Man Randy Savage Renegade or Macho... | Read more »
    Fearless Fantasy is Fantastic for the iF...
    I actually had my first look at Fearless Fantasy last year at E3, but it was on a PC so there wasn't much for me to talk about. But now that I've been able to play with a pre-release version of the iOS build, there's quite a bit for me to talk... | Read more »
    MLB Manager 2015 (Games)
    MLB Manager 2015 5.0.14 Device: iOS Universal Category: Games Price: $4.99, Version: 5.0.14 (iTunes) Description: Guide your favorite MLB franchise to glory! MLB Manager 2015, officially licensed by and based on the award-... | Read more »
    Breath of Light (Games)
    Breath of Light 1.0.1421 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.1421 (iTunes) Description: Hold a quiet moment. Breath of Light is a meditative and beautiful puzzle game with a hypnotic soundtrack by... | Read more »
    WWE WrestleMania Tags into the App Store
    Are You ready to rumble? The official WWE WrestleMania app, by World Wrestling Entertainment, is now available. Now you can get all your WrestleMania info in one place before anyone else. The app offers details on superstar signings, interactive... | Read more »
    Bio Inc's New Expansion is Infectin...
    Bio Inc., by DryGin Studios, is the real time strategy game where you infect a human body with the worst virus your evil brain can design. Recently, the game was updated to add a whole lot of new features. Now you can play the new “Lethal”... | Read more »
    The Monocular Minion is Here! Despicable...
    Despicable Me: Minion Rush, by Gameloft, is introducing a new runner to the mix in their latest update. Now you can play as Carl, the prankster minion. Carl has a few new abilities to play with, including running at a higher speed from the start.... | Read more »

    Price Scanner via

    13-inch 2.5GHz MacBook Pro (refurbished) avai...
    The Apple Store has Apple Certified Refurbished 13″ 2.5GHz MacBook Pros available for $829, or $270 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.... Read more
    Save up to $80 on iPad Air 2s, NY tax only, f...
     B&H Photo has iPad Air 2s on sale for $80 off MSRP including free shipping plus NY sales tax only: - 16GB iPad Air 2 WiFi: $469.99 $30 off - 64GB iPad Air 2 WiFi: $549.99 $50 off - 128GB iPad... Read more
    iMacs on sale for up to $205 off MSRP
    B&H Photo has 21″ and 27″ iMacs on sale for up to $205 off MSRP including free shipping plus NY sales tax only: - 21″ 1.4GHz iMac: $1019 $80 off - 21″ 2.7GHz iMac: $1189 $110 off - 21″ 2.9GHz... Read more
    Färbe Technik Offers iPhone Battery Charge LI...
    Färbe Technik, which manufactures and markets of mobile accessories for Apple, Blackberry and Samsung mobile devices, is offering tips on how to keep your iPhone charged while in the field: •... Read more
    Electronic Recyclers International CEO Urges...
    Citing a recent story on CNBC about concerns some security professionals have about the forthcoming Apple Watch, John Shegerian, Chairman and CEO of Electronic Recyclers International (ERI), the... Read more
    Save up to $380 with Apple refurbished iMacs
    The Apple Store has Apple Certified Refurbished iMacs available for up to $380 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 27″ 3.5GHz 5K iMac – $2119 $... Read more
    Mac minis on sale for up to $75 off, starting...
    MacMall has Mac minis on sale for up to $75 off MSRP including free shipping. Their prices are the lowest available for these models from any reseller: - 1.4GHz Mac mini: $459.99 $40 off - 2.6GHz Mac... Read more
    College Student Deals: Additional $50 off Mac...
    Take an additional $50 off all MacBooks and iMacs at Best Buy Online with their College Students Deals Savings, valid through April 11, 2015. Anyone with a valid .EDU email address can take advantage... Read more
    Mac Pros on sale for up to $260 off MSRP
    B&H Photo has Mac Pros on sale for up to $260 off MSRP. Shipping is free, and B&H charges sales tax in NY only: - 3.7GHz 4-core Mac Pro: $2799, $200 off MSRP - 3.5GHz 6-core Mac Pro: $3719.99... Read more
    13-inch 2.5GHz MacBook Pro on sale for $100 o...
    B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $999 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more

    Jobs Board

    DevOps Software Engineer - *Apple* Pay, iOS...
    **Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
    *Apple* Retail - Multiple Positions (US) - A...
    Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
    Sr. Technical Services Consultant, *Apple*...
    **Job Summary** Apple Professional Services (APS) has an opening for a senior technical position that contributes to Apple 's efforts for strategic and transactional Read more
    Lead *Apple* Solutions Consultant - Retail...
    **Job Summary** Job Summary The Lead ASC is an Apple employee who serves as the Apple business manager and influencer in a hyper-business critical Reseller's store Read more
    *Apple* Pay - Site Reliability Engineer - Ap...
    **Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
    All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.