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

    Geekbench 4.1.1 - Measure processor and...
    Geekbench provides a comprehensive set of benchmarks engineered to quickly and accurately measure processor and memory performance. Designed to make benchmarks easy to run and easy to understand,... Read more
    iMazing 2.3.3 - Complete iOS device mana...
    iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
    TeamViewer 12.0.81279 - Establish remote...
    TeamViewer gives you remote control of any computer or Mac over the Internet within seconds, or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for... Read more
    Safari Technology Preview 11.0 - The new...
    Safari Technology Preview contains the most recent additions and improvements to WebKit and the latest advances in Safari web technologies. And once installed, you will receive notifications of... Read more
    PDFpen 9.1 - $74.95
    PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
    GraphicConverter 10.4.3 - $39.95
    GraphicConverter is an all-purpose image-editing program that can import 200 different graphic-based formats, edit the image, and export it to any of 80 available file formats. The high-end editing... Read more
    Fission 2.3.2 - Streamlined audio editor...
    Fission can crop and trim audio, paste in or join files, or just rapidly split one long file into many. It's streamlined for fast editing. Plus, it works without the quality loss caused by other... Read more
    EarthDesk 7.2 - Striking real-time anima...
    EarthDesk replaces your static desktop picture with a rendered image of Earth showing correct sun, moon, and city illumination. With an Internet connection, EarthDesk displays near-real-time global... Read more
    Things 3.1.1 - Elegant personal task man...
    Things is a task management solution that helps to organize your tasks in an elegant and intuitive way. Things combines powerful features with simplicity through the use of tags and its intelligent... Read more
    iDefrag 5.2.0 - Disk defragmentation and...
    iDefrag helps defragment and optimize your disk for improved performance. iDefrag Features Supports HFS and HFS+ (Mac OS Extended). Supports case sensitive and journaled filesystems. Supports... Read more

    Latest Forum Discussions

    See All

    Big changes are coming to Fire Emblem He...
    Nintendo just revealed what players can expect from Fire Emblem Heroes in the coming weeks, with a good deal of fresh goodies arriving just in time for the game's six-month anniversary. The announcements arrived in a 15-minute FEH Channel stream... | Read more »
    Aero Effect (Games)
    Aero Effect 1.0.1 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0.1 (iTunes) Description: Race an aerodynamic thingy through a maze of animated, geometric peril and plunge into a mysterious grid of pixels, all while... | Read more »
    Linelight (Games)
    Linelight 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Linelight is an elegant, minimalist puzzle game set in a universe of lines. Its puzzles will awake your mind as the music flows... | Read more »
    Fighting Fantasy Legends (Games)
    Fighting Fantasy Legends 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Create your own adventures in a dangerous land of monsters, treasures and traps. From renowned authors Steve Jackson... | Read more »
    Knight Fever is a new take on the classi...
    Knight Fever lands on the Google Play, and you won’t want to miss it if you’re a devoted RPG fan. Developed by indie creatives Buff Studios, the game looks like a retro RPG with some exciting new twists. | Read more »
    Steam Panic (Games)
    Steam Panic 1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: Steampunk puzzle in which you spin the playfield to put yourself back together. Dear Reader,Enclosed is a copy of my bizarre 1935... | Read more »
    Time Crash (Games)
    Time Crash 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: Time is about to Crash! It’s up to you to save the city! Time Crash is a 3D first person runner which lets you play as a powerful... | Read more »
    Galaxy of Pen & Paper (Games)
    Galaxy of Pen & Paper 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: *** NO IN-APP PURCHASES! ****** PREMIUM FOREVER *** THE ULTIMATE ROLE-PLAYING SIMULATION GOES TO SPACE! | Read more »
    Niantic reveals more Pokémon GO legendar...
    Oof. Pokémon GO Fest Chicago was pretty much a fiasco, huh? Niantic is feeling the heat from fans for their first big event that ultimately ended in failure. So much so that they've released a much longer apology that gives a full run down of... | Read more »
    The 5 best life-saving apps for dog owne...
    While it's true that dogs are man's best friend, they're also a pretty big responsibility. We want to give our dogs the best lives, but with busy schedules that's not always easy. Luckily, though, there are a bunch of quality apps out there that... | Read more »

    Price Scanner via

    27-inch 3.5GHz iMac on sale for $100 off MSRP...
    Adorama has the new 27″ 3.5GHz iMac (MNEA2LL/A) on sale for $1899 including free shipping. Their price is $100 off MSRP. Adorama charges sales tax for purchases in NY & NJ only. Read more
    Seven Cities, One Phone: OtterBox Sends iPhon...
    Plenty of people have trekked around the world, but what about a globe-trotting iPhone? OtterBox is sending an iPhone around the world to capture the adventures of a diverse set of global ambassadors... Read more
    L-Card Pro App May Spell End For Paper Busine...
    OrangeTreeApps, LLC has announced the release of L-Card Pro 1.1, an update to their business app for iOS and Android devices that introduces eco-friendly, affordable, electronic business cards on the... Read more
    Clearance previous generation iMacs, Apple re...
    Apple has previous-generation Certified Refurbished 2015 21″ & 27″ iMacs available starting at $849. Apple’s one-year warranty is standard, and shipping is free. The following models are... Read more
    27-inch 3.4GHz iMac on sale for $1699, save $...
    MacMall has the new 2017 27″ 3.4GHz iMac (MNE92LL/A) in stock and on sale for $1699 including free shipping. Their price is $100 off MSRP. Read more
    Photographer Explains Choosing Dell Laptop Ov...
    Last week photographer and video blogger Manny Ortiz posted a video explaining the five most important reasons he settled on a Dell XPS 15 laptop instead of a MacBook Pro for his latest portable... Read more
    Sale! 10-inch iPad Pros for $50 off MSRP, no...
    B&H Photo has 64GB and 256GB 10.5″ iPad Pros in stock today and on sale for $50 off MSRP. Each iPad includes free shipping, and B&H charges sales tax in NY & NJ only: – 10.5″ 64GB iPad... Read more
    WaterField Designs Upgrades TSA-friendly Zip...
    San Francisco based designer and manufacturer Waterfield Designs has unveiled an upgraded and refined Zip Brief. Ideal for the minimalist professional, the ultra-slim Zip laptop bag actually holds a... Read more
    USB 3.0 Promoter Group Announces USB 3.2 Upda...
    The USB 3.0 Promoter Group has announced the pending release of the USB 3.2 specification, an incremental update that defines multi-lane operation for new USB 3.2 hosts and devices. USB Developer... Read more
    Save on MacBook Pros with Apple Refurbished 2...
    Apple recently dropped prices on Certified Refurbished 2016 15″ and 13″ MacBook Pros with models now as much as $590 off original MSRP. An Apple one-year warranty is included with each model, and... Read more

    Jobs Board

    *Apple* Solutions Consultant - Apple Inc. (U...
    Job Summary As an Apple Solutions Consultant, you'll be the link between our future customers and our products. You'll showcase your entrepreneurial spirit as you Read more
    Senior Software Engineer, *Apple* Online St...
    Job Summary The Apple Online Store is looking for an experienced, self-driven, detail-oriented software engineer who can join our team to help build highly scalable Read more
    Frameworks Engineering Manager, *Apple* Wat...
    Frameworks Engineering Manager, Apple Watch Job Number: 41632321 Santa Clara Valley, California, United States Posted: Jun. 15, 2017 Weekly Hours: 40.00 Job Summary Read more
    Senior Software Engineer, *Apple* Online St...
    Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
    Frameworks Engineering Manager, *Apple* Wat...
    Frameworks Engineering Manager, Apple Watch Job Number: 41632321 Santa Clara Valley, California, United States Posted: Jun. 15, 2017 Weekly Hours: 40.00 Job Summary Read more
    All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.