Abstract

Welcome to the official tutorial for Ramaze, the mandatory Todo-list.

I also assume that you have some experience with HTML and some other basics in web-development already (you want to learn a web-framework after all).

The tutorial assumes a working installation of Ruby and Rubygems.

For more information on how to install these please read the introductory documentation of Ramaze, this is not in the scope of this tutorial.

To install Ramaze you can gem install ramaze, other ways of installation are covered by the Ramaze Wiki.

Should you encounter any problems while doing this tutorial, this might either be because Ramaze changed (which happens very often while it is still young) or I actually made some mistake while writing it.

In either case it would make me (and all other poor fellows who happen to try this tutorial) very happy if you could spare some time and report the issue either on the Bug tracker , or just drop by on IRC on irc.freenode.org in the channel #ramaze.

If you have trouble with some of the terms used in this tutorial you can consult the Glossary at the end of this document.

We are also working on a book that describes Ramaze in more depth, called Journey to Ramaze, it is still very much work in progress, but some of the contents might interest you.

The repository for the book is at http://github.com/manveru/ramaze-book. Every once in a while, updates for the book will be put in HTML and PDF form at http://book.ramaze.net.

First Step, Create

The last version of this tutorial assumed a generator to produce a skeleton in which we do the work. This time around we will do everything from scratch to give you a better experience of how exactly the pieces fit together.

You can also skip all the boring learning-by-doing part and play around with the source of the todo-list example shipping with Ramaze.

Note
The example and this tutorial differ in some points, it is recommended to actually work through the tutorial first and read the example afterwards, it takes the basics taught here one step further by utilizing the Model.

You can find the example it in the examples/app/todolist/ directory of your Ramaze distribution. To find out where that is located (as this differs widely between systems), you can follow these steps in irb:

require 'rubygems'
# => true
require 'ramaze'
# => true
File.expand_path(Ramaze::BASEDIR + '/../examples/app/todolist')
# => "/home/manveru/c/ramaze/examples/app/todolist"

To start things off, we will create a basic directory structure looking like this:

.
|-- controller
|-- layout
|-- model
|-- public
|   |-- css
`-- view

Doing that is quite simple: mkdir -p controller layout model public/css view

Alright, done? Let’s go to the next step.

Second Step. Hello, World!

To make sure Ramaze is installed, and working correctly we will follow an old tradition, we create a file at the root of your application directory called start.rb with following content:

require 'rubygems'
require 'ramaze'

class MainController < Ramaze::Controller
  def index
    "Hello, World!"
  end
end

Ramaze.start

Now we run it:

delta ~/tmp/tutorial % ruby start.rb
D [2009-03-30 14:15:01 $2124] DEBUG | : Using webrick
I [2009-03-30 14:15:01 $2124]  INFO | : WEBrick 1.3.1
I [2009-03-30 14:15:01 $2124]  INFO | : ruby 1.9.2 (2009-03-02) [i686-linux]
D [2009-03-30 14:15:01 $2124] DEBUG | : TCPServer.new(0.0.0.0, 7000)
D [2009-03-30 14:15:01 $2124] DEBUG | : Rack::Handler::WEBrick is mounted on /.
I [2009-03-30 14:15:01 $2124]  INFO | : WEBrick::HTTPServer#start: pid=2124 port=7000

The logging output tells us that a server was started, listening to all connections at port 7000. If you open your browser and go to http://localhost:7000/ you should be able to see Hello, World!.

Third Step. M, like Model

Model is a term from the MVC paradigm, meaning the representation of data within your application. Ramaze doesn’t promote a particular way for this part of your application, and how you are supposed to integrate it. Since there are quite a number of ways to represent data and none is clearly superior to another, this would be both futile and short-sighted.

For the purpose of this tutorial we will use a lightweight database access toolkit for Ruby called Sequel.

Sequel is designed to take the hassle away from connecting to databases and manipulating them. Sequel deals with all the boring stuff like maintaining connections, formatting SQL correctly and fetching records so you can concentrate on your application.

Being familiar with it is not a requirement for this tutorial, but will help you tremendously when it comes to writing your own applications.

Installing Sequel is as simple as installing Ramaze: gem install sequel.

In this tutorial we are going to use the light-weight sqlite database. This requires the sqlite-ruby bindings.

You can try to gem install sqlite, which will complain if your system doesn’t provide bindings, in which case I have to refer you to http://sqlite.org.

In order to use Sequel we also need a database connection.

So we create a new file at model/init.rb with following content:

require 'sequel'

Sequel::Model.plugin(:schema)

DB = Sequel.sqlite('todolist.db')

The :schema plugin is required since Sequel 3.0, if you run a version prior to 2.12 you may remove this line if it gives you any problems.

Next we edit start.rb, remove the Hello class, and add a require for the file, start.rb should look like this now:

require 'rubygems'
require 'ramaze'

require 'model/init'

Ramaze.start

This should hook us up with a database, but anyone familiar with SQL will now ask how we are going to create our schema.

So our next step is to create the actual model for our data, for this we create another file at model/task.rb:

class Task < Sequel::Model
  set_schema do
    primary_key :id

    varchar :title, :unique => true, :empty => false
    boolean :done, :default => false
  end

  create_table unless table_exists?

  if empty?
    create :title => 'Laundry'
    create :title => 'Wash dishes'
  end
end

For this tutorial we will not bother with migrations, although Sequel does have very good support for them as well, but seriously, this is a really simple schema that probably won’t change much over the next few years.

Finally, add a line to your model/init.rb that requires model/task.rb:

require 'sequel'

Sequel::Model.plugin(:schema)

DB = Sequel.sqlite('todolist.db')

require 'model/task'

Fourth Step, V, like View

To see anything of the data in your Model we will have to add the second element in MVC, the View.

We are going to use the templating engine shipping with Ramaze, called Etanni. It has a very simple syntax compatible with SGML and XML documents.

When handling a request to /, Ramaze will automatically try to find an Action called index. Don’t bother too much about what Action means just yet, we will explain that in more detail later when we come to layouts.

To start we put some contents into view/index.xhtml (xhtml is the default filename-extension for Etanni templates)

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
  <head>
    <title>TodoList</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <h1>TodoList</h1>
    <ul>
      <?r Task.each do |task| ?>
        <li>#{ h(task.title) }: #{ task.done }</li>
      <?r end ?>
    </ul>
  </body>
</html>

The <?r ?> and #{ } elements enclose ruby code that will be executed when the template is being rendered (on every request to index). Code within <?r ?> is only executed and will not show up in the resulting document, while code within #{ } will be interpolated.

In this template we iterate over all the data stored in the Task model, yielding a list of task titles and the respective status of the task.

That wasn’t too hard, right?

Now, so we can get our instant pleasure of seeing the result of our (hard) work, let’s see how this looks like in a browser, start your application like above with ruby start.rb and open http://localhost:7000/.

The template expanded to something like (only showing the interesting part):

<ul>
  <li>Laundry: false</li>
  <li>Wash dishes: false</li>
</ul>

That wasn’t too bad, huh?

Fifth Step, C, like Controller

The last part of the MVC paradigm is the Controller. As the name indicates it gives you control over the interaction between Model and View.

Wouldn’t it be nice to have a way to add and remove items on our to-do list? Editing the model every time would be quite tiresome and problematic to do remotely.

Well, come along, I’ll give you a short intro to the concept of controllers.

In the way MVC is structured, the Controller provides the data in a nice way for the View, removing all of the data-preparation and most of the logic from the templates. This makes it firstly simple to change the front end of your application and secondly provides excellent ways of changing the complete Structure of the Model or View independent from each other.

OK, enough of the theory, you will see the benefits in an instant, first of all we will implement marking a task as done.

Go on and create the file controller/task.rb with following contents:

class Tasks < Ramaze::Controller
  map '/'

  def close(title)
    task = Task[:title => title]
    task.done = true
    task.save

    redirect_referrer
  end
end

That does following:

And we add a require to controller/task.rb to our start.rb:

require 'rubygems'
require 'ramaze'

require 'model/init'
require 'controller/task'

Ramaze.start

Next we will have to modify the view/index.xhtml to contain a link that will change the status of a task:

<ul>
  <?r Task.each do |task| ?>
    <li>
      #{ h(task.title) }: #{ task.done },
      (#{ anchor('close', 'close', task.title) })
    </li>
  <?r end ?>
</ul>

Now we have an additional link next to each task that allows us to set it to done.

An even shorter way of writing that line using default aliases, that you will encounter in other applications, is:

a('close', 'close', task.title)

But for the purpose of this tutorial we’ll try to be as explicit as possible.

Now that’s a lot of things at once, but I’m sure you will be able to keep up, the hardest part is behind us.

Don’t forget to try the new functionality in your browser, wash your dishes and do your laundry and come back for the next episode.

Sixth Step, Clean, Rinse, Repeat

Now that you have closed (and hopefully done) all of your chores, it’s time to open them again, so you won’t be without work tomorrow.

Let’s add a method to our Controller that will let us open a closed task:

class Tasks < Ramaze::Controller
  map '/'

  def close(title)
    task = Task[:title => title]
    task.done = true
    task.save

    redirect_referrer
  end

  def open(title)
    task = Task[:title => title]
    task.done = false
    task.save

    redirect_referrer
  end
end

And add a link to that action:

<ul>
  <?r Task.each do |task| ?>
    <li>
      #{ h(task.title) }: #{ task.done },
      (#{ anchor('close', 'close', task.title) })
      (#{ anchor('open', 'open', task.title) })
    </li>
  <?r end ?>
</ul>

OK, nothing new here, move along.

Oh, wait!

Rumor has it that some mad Japanese scientist got screwed by his company (they produce dishwashers), so he filed a patent for the ultimate dish washing robot that will take care of that for you.

Time to get rid of that task once and for all. No more dish washing yay!

A little modification to Controller, using destructive force.

class Tasks < Ramaze::Controller
  map '/'

  def close(title)
    task = Task[:title => title]
    task.done = true
    task.save

    redirect_referrer
  end

  def open(title)
    task = Task[:title => title]
    task.done = false
    task.save

    redirect_referrer
  end

  def delete(title)
    task = Task[:title => title]
    task.destroy

    redirect_referrer
  end
end

And a link to the delete action.

<ul>
  <?r Task.each do |task| ?>
    <li>
      #{ h(task.title) }: #{ task.done },
      (#{ anchor('close', 'close', task.title)})
      (#{ anchor('open', 'open', task.title)})
      (#{ anchor('delete', 'delete', task.title)})
    </li>
  <?r end ?>
</ul>

And dish-washing begone!

Seventh Step, More Tasks

Sure, it would be nice if life was so simple and you only have to do your laundry, but that would mean a premature end for this tutorial and an obstacle for GTD evangelists (not that they couldn’t overcome it).

So now you got a smart new robot that washes your dishes, but unfortunately it wasn’t programmed to recharge once in a while and buy soap, no biggie, we can do that with little effort, but since reddit takes up all your time you keep forgetting about it.

No problem, I say, adding following code to our view/index.xhtml will give us a nice little form that we can fill out in the few seconds between proving people on the internet wrong.

<form method="post" action="#{ route('create') }">
  <fieldset>
    <legend>Add a task by entering a title.</legend>
    <label for="form-title">Task title:</label>
    <input id="form-title" name="title" type="text" />
    <input type="submit" value="Create" />
  </fieldset>
</form>

Unfortunately, you see, this references the create action, and we have none yet. Trying to create a task will result in an error.

So what we have to do is adding one more method to our Controller that will take care of actually creating the Task.

def create
  if request.post? and title = request[:title]
    title.strip!

    unless title.empty?
      Task.create :title => title
    end
  end

  redirect route('/')
end

What is going on here?

Eighth Step, Eep, Exceptions!

So far, so good, but remember, when we defined the schema for Task we said we really want to have unique titles.

So once you created the task recharge DishBot9000 and try to create another one with the same title, you will get a nice error:

Sequel::DatabaseError: SQLite3::SQLException column title is not unique

OK, programmers ignore warnings and hide errors, let’s rescue the exception and just act as if nothing has happened.

class Tasks < Ramaze::Controller
  map '/'

  def create
    if request.post? and title = request[:title]
      title.strip!

      unless title.empty?
        Task.create :title => title
      end
    end

    redirect route('/')
  rescue Sequel::DatabaseError
    redirect route('/')
  end

  def close(title)
    # ...

Easy as pie, we can try to create as many identical tasks as we want, all we get is the same old set.

Ninth Step, Curing your RSI

Something you might notice is that every time you hit the submit button and you are redirected to index, the title you just input is gone. What a waste of our honest effort to create a duplicate task, we all know if we try often enough it will eventually have to work, so let’s save us some typing.

In our view/index.xhtml we modify the form input to have a default value:

<form method="post" action="#{ route('create') }">
  <fieldset>
    <legend>Add a task by entering a title.</legend>
    <label for="form-title">Task title:</label>
    <input id="form-title" name="title" type="text" value="#{ @title }"/>
    <input type="submit" value="Create" />
  </fieldset>
</form>

The @title is an instance-variable, those are shared between the Controller and View. We didn’t set any such variable in the Controller yet, so do it now:

class Tasks < Ramaze::Controller
  map '/'

  def index
    @title = 'recharge DishBot9000'
  end

  def create
    # ...

Yes, that wasn’t too bad, but is there a way to change the value of the @title without editing the source all the time?

Turns out we have to revisit the create method to give us a hint in form of a GET parameter and change index to pick it up.

  def index
    @title = request[:title]
  end

  def create
    if request.post? and title = request[:title]
      title.strip!

      unless title.empty?
        Task.create :title => title
      end
    end

    redirect route('/', :title => title)
  rescue Sequel::DatabaseError
    redirect route('/', :title => title)
  end

And that’s it. Endless hours of fun hitting the submit button lie before us!

Tenth Step, Laying out a different View of things

We have one template, it’s a nice one, but unfortunately we’ve got ourselves into quite a mess here after creating hundreds of tasks.

Our way out of this is to provide some visual feedback — when a task is done, it’s gone. Not forever, but at least it will not show up anymore on the index action.

So we filter out all tasks that haven’t been done yet in the view/index.xhtml:

<ul>
  <?r Task.filter(:done => false).each do |task| ?>
    <li>
      # ...

So off we go and add a new template at view/done.xhtml.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
  <head>
    <title>TodoList</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <h1>TodoList</h1>

    <form method="post" action="#{ route('create') }">
      <fieldset>
        <legend>Add a task by entering a title.</legend>
        <label for="form-title">Task title:</label>
        <input id="form-title" name="title" type="text" value="#{ @title }"/>
        <input type="submit" value="Create" />
      </fieldset>
    </form>

    <h2>Tasks done</h2>

    <ul>
      <?r Task.filter(:done => true).each do |task| ?>
        <li>
          #{ h(task.title) }: #{ task.done },
          (#{ anchor('open', 'open', task.title) })
          (#{ anchor('delete', 'delete', task.title) })
        </li>
      <?r end ?>
    </ul>
  </body>
</html>

Having a déjà vu?

Yes, me too, must be an error in the matrix.

If we want one thing from a web-framework, it’s to spare us writing repetitive code like this (I hope you did copy&paste).

What we actually wanted to do is sharing the boilerplate around our listing of tasks, that’s what we call layout.

Every action can have a layout associated with it, remember that empty layout directory in your application? That’s exactly where we will put it.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
  <head>
    <title>TodoList</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <h1>TodoList</h1>
    <form method="post" action="#{ route('create') }">
      <fieldset>
        <legend>Add a task by entering a title.</legend>
        <label for="form-title">Task title:</label>
        <input id="form-title" name="title" type="text" value="#{ @title }"/>
        <input type="submit" value="Create" />
      </fieldset>
    </form>
    #{ @content }
  </body>
</html>

And to tell Ramaze which layout to use for our Tasks we’ll have to add a line to the Controller.

class Tasks < Ramaze::Controller
  map '/'
  layout 'default'
end

And finally, since we are fond of valid HTML and just love to get rid of boring boilerplate we can delete the slack from our templates.

view/index.xhtml becomes:

<h2>Done Tasks</h2>

#{ anchor('Pending tasks', 'done') }

<ul>
  <?r Task.filter(:done => false).each do |task| ?>
    <li>
      #{ h(task.title) },
      (#{ anchor('close', 'close', task.title) })
      (#{ anchor('delete', 'delete', task.title) })
    </li>
  <?r end ?>
</ul>

view/done.xhtml becomes:

<h2>Pending Tasks</h2>

#{ anchor('Done tasks', 'done') }

<ul>
  <?r Task.filter(:done => true).each do |task| ?>
    <li>
      #{ h(task.title) },
      (#{ anchor('open', 'open', task.title) })
      (#{ anchor('delete', 'delete', task.title) })
    </li>
  <?r end ?>
</ul>

Well, that’s so much better, we even included links between the actions.

Eleventh Step, not all that is gold glitters…

You have to admit, it’s a lot of fun having such a sophisticated application, but what good is it if it’s too ugly to show it even to your closest friends? They will never become addicted enough to your fancy todo-list to actually do all the work for you.

Let’s do things with style, with a style-sheet.

Now is the time to fire up your editor, point it at public/css/screen.css and churn out something of your liking.

We will not cover this part in the tutorial, an example style-sheet is located in the example todo-list.

What we do cover is adding it to your application, or the <head> in layout/default.xhtml to be exact:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
  <head>
    <title>TodoList</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" href="/css/screen.css" />
  </head>

Voilà, you now have acquired the Certificate of Ramazeness and all your friends and enemies envy you.

Twelfth Step, configuring configurable configurability

To round up this tutorial a bit, let’s introduce you to configuration in Ramaze. There are a number of ways to configure Ramaze, but here we’ll just see the most common ones with some options you’ll most likely want to change.

First of all, you have been running your ramaze application always on the same port, 7000, which prevents you from starting more than one instance or other applications.

To change the port, you can, for example:

Ramaze.options.adapter.port = 80
Note
Running a server on a port below 1024 will require root privileges and is generally not advised for applications that don’t drop their privileges after establishing a connection. Please have a look at http://wiki.ramaze.net/Deployment for better ways to deploy your site using a reverse proxy like apache, lighttpd, or nginx.

OK, a different port is fine, but how about some speed-boost? For this we will need a faster server like Mongrel or Thin.

You can install either one via:

gem install thin
gem install mongrel

Now to the configuration:

# The default is WEBrick
Ramaze.options.adapter.adapter = :webrick

# How about using Mongrel instead?
Ramaze.options.adapter.adapter = :mongrel

# Or maybe Thin?
Ramaze.options.adapter.adapter = :thin

For the full performance, switch Ramaze into :live mode:

# The default is :dev
Ramaze.options.mode = :live

# And here comes :live
Ramaze.options.mode = :live

The major differences between :dev and :live are that in :live mode your code won’t be automatically reloaded if it has changed and we don’t run every request through Rack::Lint, which helps you to stay within the request/response specifications required by Rack.

Glossary

RDBMS

Relational Database Management System

ORM

Object Relationship Mapper: Maps data into objects and assists in querying and manipulation

MVC

Model, View, Controller: one of the patterns traditionally used for GUIs in Smalltalk.

Etanni

Innate spelled backwards.

Innate

Core of Ramaze.

Rack

HTTP abstraction layer and interface used by the majority of Ruby web-frameworks.

Templating engine

Used to process so-called templates with inlined source code or instructions to produce dynamic resulting documents. Examples for traditional templating engines are XSLT, SSI, ERB.

RSI

Repetive Strain Injury, prevalent among the members of the church of Emacs.