How to make a todo list program with Rails

A tender way to get started with a tender webframework

Vincent Foley (vfoleybourgon@yahoo.ca), 21 Dec 2004, rev. 4

Introduction

This tutorial is intended for first-time users of Rails who want to build a small database-driven application. I’ve chosen to show the creation of a rather simple TODO list program, because it’s short, easy and can be useful. I’ll be using Rails version 0.9.1, with ActionPack 1.0.1, ActiveRecord 1.2.0 and ActionMailer 0.5. If you have any problems, email them to me. If you want information on the methods and classes used in this tutorial, you can visit the Rails API documentation.

Installation

In this tutorial, I’ll be using WEBrick as the webserver (comes with Rails, so don’t worry if that didn’t ring a bell) and the MySQL database. In order to be able to build the application, you will need a few software packages. If you are using the Debian Linux distribution, you can visit the Rails On Debian page on the official Ruby on Rails website for informations on which packages you need to install to be up and running. If you are using another distribution or another operating system such as Windows or MacOS, you’ll need to download Ruby’s bindings to MySQL. Then, you’ll want to actually install Rails, to do so, you can visit the page Gem Rails to know how to install the needed packages with Ruby’s package manager, RubyGems. If you run into any problems for installation, you can go to the #rubyonrails IRC channel on the FreeNode network to get further help.

Creating the table

Let’s assume that you have successfully installed Rails, we’ll now get into the meat of the subject, how to build your TODO list program.

The first thing we’ll think about is the database model. Since this is an introductory tutorial, we’ll keep the thing really simple, and have a single table with only three (3) columns: id, description, done. Since I’m really lazy and I don’t like creating SQL tables and databases by hand, I use phpMyAdmin (OSX users can use the excellent CocoaMySQL), but I will give here the SQL query to create our database and table:

-- Create the database
CREATE DATABASE `todo` ;

-- Create the table
CREATE TABLE `todos` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `description` VARCHAR( 100 ) NOT NULL ,
  `done` TINYINT DEFAULT '0' NOT NULL ,
  PRIMARY KEY ( `id` )
);

So, every todo item will have a unique id (by the way, with Rails it is strongly suggested that you always use ‘id’ as your primary key in a table, helps a whole lot) a description and a done flag that indicates whether an item has been done or not.

Configuration

Now that our table is created, let us start our project. Go into a directory of your choice and run the following command:
$ rails Todo

This will output a big listing describing the creation of a bunch of directories and files, setting permissions, etc. Once that is done (should take less than a second), go into that newly created directory.

Now, we will start by editing the main configuration file, config/database.yml. This is what it looks like at first:
development:
  adapter: mysql
  database: rails_development
  host: localhost
  username: root
  password: 

test:
  adapter: mysql
  database: rails_test
  host: localhost
  username: root
  password:

production:
  adapter: mysql
  database: rails_production
  host: localhost
  username: root
  password: 
We will modify database.yml to use our database. Modify the development section to this:
development:
  adapter: mysql
  database: todo
  host: localhost
  username: root
  password:

Now our Rails application will use the MySQL database todo. Be sure to modify the username and password fields if you have configured new ones. Refer to the MySQL documentation for instructions on how to do add users to MySQL.

Coding

First steps

The next step is to create a model and a controller for our applications. To do this, there is a script in the script/ directory called generate. Run these commands:

$ ruby script/generate model Todo
$ ruby script/generate controller todo

The name of the model must always start with a capital letter, and by convention the name of your model should be the singularization of the name of your table (e.g: the todos table should have a Todo model) so that ActiveRecord knows which controller to associate with which table.

Now, we will “link” our controller to our model, open app/controllers/todo_controller.rb and modify the file so that it’s like this:

class TodoController < ApplicationController
  model :todo
end

This is pretty self explainatory: the model of our TodoController class is :todo. :todo is a Ruby symbol representing our model (the formula is colon plus the lowercase spelling of our model.)

Now, let’s start the WEBrick server and try to see our application. in the /Todo directory, run the following command:
$ ruby script/server

This will start the WEBrick server in development mode on port 3000. Point your browser to http://localhost:3000 and you should see the default Rails page which basically congratulates us for a successful configuration. This message is not really destined to us though, because thanks to WEBrick, there is no configuration :) Congratulations to the people who succeeded in configuring Apache correctly though.

You can use the -h flag to see the different options available in the server.

Controllers and actions

This is cute, but it’s not what we want, we want to see our application! So let’s add todo/ at the end of the URL. Note that the way to access to a controller is just to add its name to the URL. So, what does that give us?

What is this message? Well, it says we don’t have an index action (index is the default action of a controller). An action is a method of a controller in Rails by the way. So, how can we fix this problem? Let’s open up app/controllers/todo_controller.rb and add the following lines below the model :todo line:

def index
end

This is an empty method definition. In Rails, Ruby methods are actions, so now that we have an index action, what will happen? To know the answer, reload your web browser.

Template missing? What is that? It means that our action exists and that it works, but it doesn’t have anything to show us. Let’s remedy that and use a method called render_text, change the index method to this:

def index
  render_text("This is the index")
end

Let’s reload the page again and see what we get now.

Scaffolding

Yipee! It works! However, it’s not terribly useful for managing todo items, is it? Let us try something else, delete the index method we’ve just created and put this line in its place:

scaffold :todo

scaffold is a method to create a simple database interface for us, automatically. The argument, :todo is a symbol (a symbol in Ruby starts with a colon) with the name of our model. What happens if we reload our index page?

You can’t tell since we don’t have any data, but this new page would display all the entries in our table. scaffold creates many actions: new, edit, destroy, list, show and index. index just redirects to list, list shows every item in the database, show gives us only one, new creates a new row and edit lets us update a row. Play with the scaffold pages to understand what’s in there. You can surely see that this is an extremely easy and fast way (unless typing scaffold :todo takes 10 hours and 3 programmers) to set up a basic database access application. Scaffolding is a great way to enter initial data in your database.

generate scaffold

Another way to do scaffolding is to have the actual code in our controller and views. The generate script has a scaffold action that you can use to put the entire code in your controller and views. You would use it with the following command:

$ ruby script/generate scaffold Todo

You can check it out after this tutorial. For now, we’ll just stick with the scaffold :todo line.

Displaying the items: part I

The problem with scaffolding is that it’s very general and does not really work well for managing a todo list. If you added a new item, you probably saw that you had to enter both the description and the done flag. However, a done flag should always be off when adding an item (right?), so there’s no need to ask the user for that information. Whatsmore, asking the user to input a number to mark a flag is kind of awkward, wouldn’t it be much nicer to have a checkbox? So we’ll write our own actions to make our application nicer to use.

We’ll leave the scaffold line in our file, so we can modify entries, remove them, etc. easily, but we will start adding our own methods.

The first action we’ll make is list to view all our items at once. So in the file app/controllers/todo_controller.rb, add this method declaration:

def list
end

and point your browser to http://localhost:3000/todo/list. You’ll note that we no longer have the page scaffold made for us, but rather our “template missing” page. What is this template missing thing again? Well we don’t have a file to display our action into. Let’s remedy that and create a blank file in app/views/todo/ called list.rhtml (note, this is .rhtml, not .html) and if you reload your browser, you should have a blank page. The name of the view must match that of the action you want it to be associated with.

Now, how do we get our entries into that page? Well, let’s add code to list.rhtml. Copy this code into list.rhtml:

<html>
  <head>
    <title>My todo list</title>
  </head>

  <body>
    <% @items.each do |@item| %>
    <%= @item.description %>
    <br />
    <% end %>
  </body>
</html>
And change the list method in app/controllers/todo_controller.rb to this:
def list
  @items = Todo.find_all
end

Now reload your browser.

If you added any items while you played with scaffolding, you should see them displayed, if you didn’t, go to http://localhost:3000/todo/new and add an item.

Now, let’s explain what exactly we did here. We’ll start with the action, because there’s only one line to understand. We basically find all rows in the todo table and put them in an instance variable (denoted by the prefix @) called @items.

In the view, we have pretty standard HTML code, except in the body where we have three weird lines. The <% %> and <%= %> tags are ERb tags, and the code between them is Ruby code. The difference between the two is that <%= %> puts its result into the HTML document while <% %> doesn’t.

The view has access to the instance variables declared inside the action method, so this is why we can access the @items variable. We use a Ruby construct called #each to loop over every element of @items and we put them into @item. The next line is used to display the description of the item. description is a method that was dynamically created because we have a description column in our todo table and item is an instance of the model for that table. If our table had a column called chunky_bacon, it would be possible to say @item.chunky_bacon. If we didn’t call description, the object representation would’ve been displayed, and that looks like #<Todo:0×404ca038>, which is not terribly useful. And finally, we close the loop with end in a pair of tags.

Okay, now it would be nice to be able to see whether an item has been done or not. As I was saying earlier, a little checkbox to the left of the description of the item would be quite nice. Now, we need to have a checkbox that is checked when item is done (when the done field is set to 1) and unchecked when it’s not (done set to 0). Fortuneatly, Rails provides us with such a method in ActionView that is called check_box. Let’s open our app/views/todo/list.rhtml file again and add this line of code above the one to display the item’s description:

<%= check_box("item", "done") %>

What does this line tell us? The first string given to check_box is the variable we want to fetch something from (@item in our case) and what method getter for the information we want. check_box automatically takes care of putting the field “checked” in the HTML code if @item.done returns a value above 0. Let’s reload our page and see what it looks like:

Very cute, however, does it work if @item.done returns 1? Let’s find out, we’ll start by adding a link which will automatically take us to the editing view that scaffold made for us. After the @item.description line in app/views/todo/list.rhtml, add the following line:
<%= link_to("Edit", :action => "edit", :id => @item.id) %>

Okay, let’s explain this line. link_to create an hypertext link. The first parameter is the label that should appear on the screen (the part between the tags in HTML.) The next parameters are part of a dictionary of options. The :action => "edit" part means that this link should point to the edit action of our controller (the URL will be http://localhost:3000/todo/edit) and the :id => @item.id is to tell the edit action which item we want to modify, so we give the id parameter of the item (which makes the URL http://localhost:3000/todo/edit/x where x is the id of our item.) Let’s see what it gives on screen.

Click on edit to go the edit page, and change the done field from 0 to 1. Click the update button and then the back link to go back to the list view.

Et voil! Our checkbox perfectly adapts to whether an item has been marked as done or not.

New items

We’ll worry later about making our checkbox change the done flag value. Now, let’s see how to add a new item. Since an item is basically just a short line, we don’t need to make a whole page for new entries, so we’ll just put it in the list view (file list.rhtml.)

We’ll start by adding the following code to our view. Add this after the <% end %> line:

<hr size=3>                                                   
  <form method="post" action="add_item">
    New item:                                                   
    <%= text_field("new_item", "description") %>                
    <input type="submit" value="Add item">
  </form>

This adds a simple form at the bottom of the page to input new items. Note how you can perfectly mix and match ERb code and HTML code. text_field is a FormHelper method to make text input fields easier. They can load data from the table, and they make it easy to add new data, which we’ll see in just a moment. The first argument is that of an instance variable and the second of a field. If such a variable has been created in the controller and that it has a field as per the second argument, the value of that field would be transfered in the text zone. Since we did not create such a variable, ours will be empty (and that’s what we want.) However, when we’ll click our Add item button, a sub-hashtable will be created in @params with new_item as the key. @params is a variable that is sent in a form action. We’ll add an action to see what @params looks like. Add this method to your controller:

def add_item 
  render_text @params.inspect
end

Reload your list page, input something in the text zone and press the Add item button.

This is what @params looks like. Do you see where new_item and description we specified in our text_field are? Now, let’s change the add_item method to actually add the item. Put this code in place of the render_text line:

def add_item
  item = Todo.new  # Create a new instance of Todo, so create a new item
  item.attributes = @params["new_item"]  # The fields of item should be set to what's in the "new_item" hash

  if item.save  # Try to save our item into the database
    redirect_to(:action => "list")  # Return to the list page if it suceeds
  else
    render_text "Couldn't add new item"  # Print an error message otherwise
  end
end

Wow, our biggest method to date! Okay, what does this do now? Well, read the comments :)

Destroying items

Okay, now that we can add items, we might want to remove them (maybe they’re quite old, maybe, we don’t have to do a certain task anymore, etc.) Destroying an item is not terribly hard. Open your view app/views/todo/list.rhtml and add the following line below the Edit line:

<%= link_to("Destroy", :action => "destroy", :id => @item.id) %>

I don’t believe I need to explain this line, since it’s almost identical to the Edit line just above it. If you don’t remember what this does, go see how Edit worked.

And also add this to your controller (app/controllers/todo_controller.rb):
def destroy
  item = Todo.find(@params["id"])
  begin
    item.destroy
    redirect_to(:action => "list")
  rescue ActiveRecord::ActiveRecordError
    render_text "Couldn't destroy item" 
  end
end

This is pretty straight-forward and easy to understand. We select the item we want to delete (because we got its unique id), and then we try to destroy it. If it works, we return to the list, otherwise, we inform the user.

This destroy method has one flaw though: it doesn’t ask the user if he/she really meant to delete the item. Let’s remedy that by changing the destroy line in app/views/todo/list.rhtml:

<%= link_to("Destroy",
            { :action => "destroy", :id => @item.id },
            :confirm => "Are you sure you want to delete this entry: #{@item.description}") %>

Okay, what have we changed? Well, we put braces around the arguments we already had, those are the options. And then, we have the html_options, and one of those is :confirm which prompts a small JavaScript box with a “Yes/No” button pair. If the user presses “Yes”, Rails takes us to the link, if the user presses “No”, Rails doesn’t do anything. Let’s take a look:

Yay! We never have to pay bills again!

Changing the done flag

Okay, the next task for us will be to make that done checkbox work. We will need to use a bit of JavaScript for this to work. Let’s start by modifying the check_box call in app/views/todo/list.rhtml:

<%= check_box("item", "done", "onclick" => "document.location.href='/todo/toggle_check/#{@item.id}'") %>

The JavaScript part is the "onclick" part. This tells the browser to go to a URL of our choice; the URL of our choice is /todo/toggle_check/#{@item.id} where, if you remember from earlier, todo is the controller, toggle_check is the action (this means we’ll need a toggle_check method) and #{@item.id} is a variable which holds the unique id of our item. In HTML source, this gives the following result (for an item of id 2):

<input checked="checked" id="item_done" name="item[done]" onclick="document.location.href='/todo/toggle_check/2'" type="checkbox" value="1" />

Now, we need to write the method toggle_check to make the change in the database. It’s rather simple really, if our item has a done flag set at 1, we change it to 0, otherwise we change it to 1.

def toggle_check
  item = Todo.find(@params["id"])

  # change the done flag.  ActiveRecord
  # will automatically do the boolean/int
  # conversion.  
  item.done = !item.done?

  if item.save
    redirect_to(:action => "list")
  else
    render_text "Couldn't update item" 
  end
end

Easy, isn’t it? I don’t think there is much to explain in here, it’s very similar to other methods we’ve already written. You can now reload your browser and try that little checkbox and see if it works. No screenshots here though, it’s kind of hard to see me clicking on a checkbox in a screenshot.

Displaying the items: part II

Okay, now that we can add new items, edit them with the nice scaffolding page, destroy them and check them, let’s rethink display. Right now, the items are all put together, one after the other, ordered by id. On a notebook this is okay, but a computer can sort things easily for us, so we’ll do two things:
  1. Seperate done items from undone items
  2. Order the items by description (alphabetical order)

Let’s go into our controller (app/controllers/todo_controller.rb) and we’ll modify a few things in the list method. Make the list method look like this:

def list                                                        
  @not_done = Todo.find_all("done = 0", "description")          
  @done = Todo.find_all("done = 1", "description")              
end                                                             

If you give parameters to find_all, the first one is a filter. If it’s nil, no filter is assumed. In our case, we tell that we want only the items with a done value of 0 in @not_done and only the items with a done value of 1 in @done. The second parameter is the SORT BY parameter. If nil, id is assumed.

However, we are dealing here with things that relate more to the model than to the controller, so it would probably be good to put the routines to find done and not_done items in our model file. So open up app/models/todo.rb and add these two methods to the class:

def self.find_not_done
  find_all("done = 0", "description")
end

def self.find_done
  find_all("done = 1", "description")
end

These are class methods (denoted by the leading self.) that find the items matching the done flag and order them by description. You can now use these in your controller and views. Let’s modify our list method in our controller, so that it now reads this:

def list
  @not_done = Todo.find_not_done
  @done = Todo.find_done
end

It’ll work the same as above, but we moved our constraints to the model, which is where they really belong.

So, now that we have our two containers with the values we want, let’s display them.

For this part, I will start doing it the obvious, but ugly way. This is the whole new file that you want:

<html>
  <head>
    <title>My todo list</title>
  </head>

  <body>
    Not done:<br />
    <% @not_done.each do |@item| %>
    <%= check_box("item", "done", "onclick" => "document.location.href='/todo/toggle_check/#{@item.id}'") %>
    <%= @item.description %>
    <%= link_to("Edit", :action => "edit", :id => @item.id) %>
    <%= link_to("Destroy",
                { :action => "destroy", :id => @item.id },
                :confirm => "Are you sure you want to delete this entry: #{@item.description}") %>
    <br />
    <% end %>

    <p>
    <hr size=3>
    <p>

    Done:<br />
    <% @done.each do |@item| %>
    <%= check_box("item", "done", "onclick" => "document.location.href='/todo/toggle_check/#{@item.id}'") %>
    <%= @item.description %>
    <%= link_to("Edit", :action => "edit", :id => @item.id) %>
    <%= link_to("Destroy",
                { :action => "destroy", :id => @item.id },
                :confirm => "Are you sure you want to delete this entry: #{@item.description}") %>
    <br />
    <% end %>

    <hr size=3>
    <form method="post" action="add_item">
      New item:
      <%= text_field("new_item", "description") %>
      <input type="submit" value="Add item">
    </form>

  </body>
</html>

And the result:

Well, it works, but some readers must feel terrible about the code in our view, it’s basically the same code, except of the name of the array containing the items. How do you guys feel about a little refactoring? This will make the file much shorter and cleaner. We will use a helper file to write a method that we will call in our view. Open the file app/helpers/todo_helper.rb and add this method:

def display_items(ary)
  result_string = "" 
  ary.each do |@item|
    result_string << check_box("item", "done", "onclick" => "document.location.href='/todo/toggle_check/#{@item.id}'") + " " 
    result_string << @item.description + " " 
    result_string << link_to("Edit", :action => "edit", :id => @item.id) + " " 
    result_string << link_to("Destroy",
                             { :action => "destroy", :id => @item.id },
                             :confirm => "Are you sure you want to delete this entry: #{@item.description}")
    result_string << "<BR />" 
  end
  return result_string
end

Since every one of the methods we use in our views returns text, we just add everything inside a string and then we return that string and it’ll be processed and displayed by the Rails dispatcher. The space added at the end of the first three lines are to put a space between the elements, otherwise they’d all be stuck together. And our new app/views/todo/list.rhtml file looks like this now:

<html>
  <head>
    <title>My todo list</title>
  </head>

  <body>
    Not done:<br />
    <%= display_items(@not_done) %>

    <p>
    <hr size=3>
    <p>

    Done:<br />
    <%= display_items(@done) %>

    <hr size=3>
    <form method="post" action="add_item">
      New item:
      <%= text_field("new_item", "description") %>
      <input type="submit" value="Add item">
    </form>

  </body>
</html>

That’s considerably shorter, isn’t it? And it should please the people who felt bad about the code repetition we had earlier. If you have a lot of code in your view, you should strongly consider moving it to a helper method.

Displaying the items: using partials

There is another way with which we can display our items, and it’s with the help of partials. Partials are basically sub-templates which can be included into a view. Partials are easy to distinguish from regular templates, because their name begins with an underscore (_). Let’s see how we can use partials to display our items. First, you’ll need to create a file called app/views/todo/_display.rhtml (the leading underscore is essential):

<%= check_box("item", "done", "onclick" => "document.location.href='/todo/toggle_check/#{@item.id}'") %>
<%= @item.description %>
<%= link_to("Edit", :action => "edit", :id => @item.id) %>
<%= link_to("Destroy", { :action => "destroy", :id => @item.id },
:confirm => "Are you sure you want to delete this entry: #{@item.description}") %>
<br>

We’ll now modify our app/views/todo/list.rhtml file to use our partial. Remove the lines with our helper method call, and put these three lines in their place:

# Not done section
<% @not_done.each do |@item| %>
<%= render_partial "display", @item %>
<% end %>

# Done section
<% @done.each do |@item| %>
<%= render_partial "display", @item %>
<% end %>

The render_partial method takes a first string argument which is the name of your partial (the name of your file, minus the leading underscore and the extension) and an item to display (if you look into our partial, you’ll see that we use a @item variable.) You can now reload your web browser, and your should have the exact same thing as before.

This looping over a collection and displaying them in a partial is such a common thing that there’s a method to do just that called render_collection_of_partials. In your controller, remove the lines we’ve just added, and put those instead:

# Not done section
<%= render_collection_of_partials "display", @not_done %>

# Done section
<%= render_collection_of_partials "display", @done %>

And you’ll need to add a line to app/views/todo/_display.rhtml:

<% @item = display %> <!-- Add this line -->

<%= check_box("item", "done", "onclick" => "document.location.href='/todo/toggle_check/#{@item.id}'") %>
<%= @item.description %>
<%= link_to("Edit", :action => "edit", :id => @item.id) %>
<%= link_to("Destroy", { :action => "destroy", :id => @item.id }, :confirm => "Are you sure you want to delete this entry: #{@item.description}") %>
<br>
The reason why we added this line is two-fold:
  1. The name of an individual element when using render_collection_of_partials is that of the partial, and I think that display is a particularily bad name to represent a todo item;
  2. We use check_box, and thus we need to use an instance variable (variable that starts with @), because the first parameter is the string representation of an instance variable. If we did not use one, our checkboxes would always be empty.

Now, if you reload the page, you should still have the same thing.

Which of helpers or partials is better? I guess that depends on each and every person, however partials have been recently added to Rails to make it easy to display stuff without using helpers or clogging your template with such code.

Links

Rails: Ruby on Rails Rails documentation Rails Academy Testing with Ruby on Rails (PDF) Vim syntax for eRuby Loud Thinking #rubyonrails The Rails mailing list Ruby:

Thanks

I would like to thank some people who helped make this tutorial: David Hansson-Heinemeier for Rails, duh :) #rubyonrails: all the guys on the IRC channel are extremely helpful and fun and really helped me get started with Rails Jamis Buck for the neat Ruby script to generate a much better looking page Jonathan Paisley for making some nice suggestions such as talking about the -c option of dispatch.servlet Robert Bousquet for pointing our a bug with item.destroy Christian Metts for suggesting the use of partials Peter Kjr Monsson for mirroring this tutorial Josh Goebel for all the help with the CSS

And finally, a big thank you to all the people who decided to try a little-known framework to build web applications, found a tutorial written by a weird french Canadian, tried it and wrote about it on their blogs. I found this tutorial linked on english, french, italian and spanish blogs, thank you so much.

Conclusion

Well, that’s the end of this tutorial on Rails, I hope it helped you. Feel free to send questions, comments, correction to me. Also be sure to visit us on IRC on the #rubyonrails channel on the Freenode servers, we like to have fun and help people!