Singing with Sinatra

Last week I started playing around with a very cool Ruby web framework called Sinatra. It is an easy way to create a fast RESTful ruby web application with few lines of code that is easy to setup.

Sinatra is built on top of Rack, which provides a minimal API for connecting web servers and web frameworks. Read Chris Neukirchen blog article to understand more about how Rack works.

Let’s look at the classic “Hello World” application with Sinatra:

#hello.rb
require 'rubygems'
require 'sinatra'
get '/' do
  'Hello world!'
end

To run you just open a terminal window:

$ ruby hello.rb
== Sinatra has taken the stage on port 4567 for development

Wow, I can dance to that! Now let’s build the Web 2.0 version of “Hello World”, the blog.

Before Sinatra can sing, we need to setup the stage:

Install Ruby (if you do not already have it)

$ curl -O ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.6.tar.gz
$ tar xzvf ruby-1.8.6.tar.gz
$ cd ruby-1.8.6
$ ./configure --prefix=/usr/local --enable-pthread --with-readline-dir=/usr/local --enable-shared
$ make
$ sudo make install
$ sudo make install-doc
$ ruby -v
$ ruby 1.8.6

Install SQLite:

$ wget http://www.sqlite.org/sqlite-3.5.4.tar.gz
$ tar xvfz sqlite-3.5.4.tar.gz
$ cd sqlite-3.5.4
$ ./configure --prefix=/usr/local
$ make
$ sudo make install

Install Gems

$ sudo gem install sinatra
$ sudo gem install datamapper
$ sudo gem install do_sqlite3
$ sudo gem install --no-ri haml

TextMate Haml bundle (optional)

$ cd /Applications/TextMate.app/Contents/SharedSupport/Bundles
$ svn co "http://svn.textmate.org/trunk/Bundles/Ruby%20Haml.tmbundle"

Now let’s create our directory structure:

$ mkdir my_way
$ cd my_way/
$ touch blog.rb
$ mkdir db
$ mkdir views
$ cd views/
$ touch articles.haml article.haml article_new.haml article_edit.haml layout.haml style.sass
$ cd ..
$ cd ..
$ mate my_way

The last command opens the directory in TextMate, if you are not using TextMate you can open in any editor you wish.

Testing things out:

To make sure we have everything setup, open blog.rb and add the following:

#blog.rb
require 'rubygems'
require 'sinatra'
require 'data_mapper'
require 'Time'
get '/articles' do
  "We are dancing now!"
end

Then from terminal:

$ cd my_way/
$ ruby blog.rb
== Sinatra has taken the stage on port 4567 for development

Now open a browser window to http://localhost:4567/articles and you should see:

We are dancing now!

Setting up our database

Sinatra is ORM agnostic so you can use whatever ORM you like, but for our app we are going to use DataMapper, which is very fast, thread-safe and feature rich. Let’s setup our database, add the following to the top of blog.rb after the require lines.

DataMapper::Database.setup({
  :adapter  => 'sqlite3',
  :host     => 'localhost',
  :username => '',
  :password => '',
  :database => 'db/my_way_development'
})

This configures DataMapper to use SQLite and store the database file in our db directory.

Creating our models

Now add the following model definition:

class Article < DataMapper::Base
  property :title, :text
  property :text, :text
  property :posted_by, :string
  property :permalink, :text
  property :created_at, :datetime
  property :updated_at, :datetime
end

A model’s properties are not derived from database structure. Instead, properties are declared inside it’s model’s class definition, which map to (or generate) fields in a database.

Defining properties explicitly in a model has several advantages. It centralizes information about the model in a single location, rather than having to dig out migrations, xml, or other config files. It also provides the ability to use Ruby’s access control functions. Finally, since Datamapper only cares about properties explicitly defined in your models, Datamappers plays well with legacy databases and shares databases easily with other applications.

Now that we have our model defined let’s tell DataMapper to create our table, add the following after our model definition.

database.table_exists?(Article) or database.save(Article)

This tells DataMapper to create our table if it does not already exist.

Defining our controller actions

One of the coolest things about Sinatra is that you do not have to create an entire controller class for a simple application, you just have to simply define your actions. If you come from the Python world Sinatra is similar to web.py, but with much cleaner DSL syntax.

For our blog we need to list articles, add the following to blog.rb, we have already defined this block when we were testing things out, just replace the body with this new code:

get '/articles' do
  @articles = Article.all :limit => 10,
                          :order => 'created_at desc'
  haml :articles
end

We ask our Article model to give us the top 10 articles in descending order. DataMapper has a very clean DSL, but also supports much of ActiveRecord syntax for finders, so aside from mapping your columns and changing the base class that your models inherit from the transition is easy.

Next we call the haml method which will invoke our articles partial that we will define shortly. We do not have to pass our @articles instance variable to our partial, because templates are rendered in the context the current Sinatra::EventContext. This means you get all instance/class variables and methods it has access to.

Sinatra has built in support for Haml, but what is it? Haml is a markup language that’s used to cleanly and simply describe the XHTML of any web document, without the use of inline code. Haml functions as a replacement for inline page templating systems such as PHP, ERB, and ASP. However, Haml avoids the need for explicitly coding XHTML into the template, because it is actually an abstract description of the XHTML, with some code to generate dynamic content. We will get more into Haml when we create our views, but let’s create our remaining controller actions.

To view an article:

get '/article/:permalink' do
  @article = Article.find :first,
                          :permalink => params[:permalink]
  view :article
end

To create a new article:

get '/articles/new' do
  view :article_new
end
post '/articles/create' do
  @article = Article.new :title     => params[:article_title],
                         :text      => params[:article_text],
                         :posted_by => params[:article_posted_by],
                         :permalink => create_permalink(params[:article_title])
  if @article.save
    redirect "/article/#{@article.permalink}"
  else
    redirect "/articles"
  end
end

To edit an article:

get '/article/edit/:permalink' do
  @article = Article.find :first,
                          :permalink => params[:permalink]
  view :article_edit
end
post '/article/update/:permalink' do
  @article = Article.find :first,
                          :permalink => params[:permalink]
  if @article
    @article.title = params[:article_title]
    @article.text = params[:article_text]
    @article.posted_by = params[:article_posted_by]
    @article.updated_at = Time.now
    if @article.save
      redirect "/article/#{@article.permalink}"
    else
      redirect "/articles"
    end
  else
    redirect "/articles"
  end
end

Since we are calling the haml method within each method we should create a private helper method for displaying our partial, we will call it view, this also helps if want to easily switch over to erb:

helpers do
  def view(view)
    haml view
    #erb view
  end
end

Creating our views with Haml

Now we can move on to creating our views. Open each of .haml files that we created earlier and add the following haml code.

First our ./views/articles.haml:

%div{:class => "articles"}
  %div{:class => "article"}
    - @articles.each do |article|
      %h2
        %a{:href => "/article/#{article.permalink}" }
          = article.title
      %div{:class => "article_text"}
        = article.short_text
      %span{:class => "article_by"}
        %h4= article.written_by
      %span{:class => "article_created_at"}
        %h4= article.written_on
      %span{:class => "article_tags"}
        Tags:
        -article.tags.each do |tag|
          = tag.name
  %a{:href => "/articles/new"} New Article

Next ./views/article.haml:

%div{:class => "articles"}
  %div{:class => "article"}
    %h1= @article.title
    %div{:class => "article_text"}
      = @article.text
    %span{:class => "article_by"}
      %h4= @article.written_by
    %span{:class => "article_created_at"}
      %h4= @article.written_on
    %span{:class => "article_tags"}
      Tags:
      -@article.tags do |tag|
        = tag.name
    %br
    %a{:href => "/article/edit/#{@article.permalink}"} Edit
    or
    %a{:href => "/articles" } Back

Then ./views/article_new.haml:

%div{:id => "new_article"}
  %h3 New Article
  %form{:id => "article_form", :method => "post", :action => "/articles/create"}
    %label Title:
    %br
    %input{:id => "article_title", :name => "article_title", :type => "text", :value => ""}
    %br
    %label Text:
    %br
    %textarea{:id => "article_text", :name => "article_text", :cols => "40" , :rows => "15"}
    %br
    %lable Posted by:
    %br
    %input{:id => "article_posted_by", :name => "article_posted_by", :type => "text", :value => ""}
    %br
    %label Tags
    %br
    %input{:id => "article_tags", :name => "article_tags", :value => ""}
    %br
    %input{:type => "submit", :value => "Post"}

Next ./views/article_edit.haml:

%div{:id => "new_article"}
  %h3 New Article
  %form{:id => "article_form", :method => "post", :action => "/articles/create"}
    %label Title:
    %br
    %input{:id => "article_title", :name => "article_title", :type => "text", :value => ""}
    %br
    %label Text:
    %br
    %textarea{:id => "article_text", :name => "article_text", :cols => "40" , :rows => "15"}
    %br
    %lable Posted by:
    %br
    %input{:id => "article_posted_by", :name => "article_posted_by", :type => "text", :value => ""}
    %br
    %label Tags
    %br
    %input{:id => "article_tags", :name => "article_tags", :value => ""}
    %br
    %input{:type => "submit", :value => "Post"}

Finally ./views/layout.haml:

!!! Strict
%html{:xmlns => "http://www.w3.org/1999/xhtml"}
  %head
    %title
      = @title || 'My Way - sung my Sinatra!'
    %link{:rel => "stylesheet", :href => "/application.css", :type => "text/css", :media => "all"}
  %body
    %div{:id => "contents"}
      %span{:id => "logo"}
      %span{:id => "title"} The Sinatra Way
      = yield

Haml is petty straight forward and you should be able to easily read the haml DSL to undersand what is going on here, but with a quick review of the docs you will be hacking on Haml like pro.

Creating some style with Sass

Finally, we will be using Sass for our style markup. Sass is a meta-language on top of CSS that’s used to describe the style of a document cleanly and structurally, with more power than flat CSS allows. Sass both provides a simpler, more elegant syntax for CSS and implements various features that are useful for creating manageable stylesheets.

First we need add another get method to our blog.rb file:

get '/application.css' do
  header 'Content-Type' => 'text/css; charset=utf-8'
  sass :style
end

This will map the get request for application.css, that is defined in our ./views/layout.haml, to our ./views/style.sass file. As with Haml Sinatra has built in support for Sass. Now all we have to do is add some Sass DSL to our ./views/style.sass:

// Constant Definitions
!primary_color = #fff
!primary_font_size = 1em
// Global Attributes
body
  :font
    :family fantasy
    :size= !primary_font_size
    :weight bold
  :background
    :position center top
    :color= !primary_color
    :repeat repeat

One of the coolest features of Sass is that you can create constants of common values, using the !constant_value syntax, that can be used throughout file, no more doing a search and replace if you need to change a common color or font size.

Well that is all we have time for now but, if you wish you can follow along with continued development of this blog by following my GitHub project or if you just want to clone the project locally:

$ git clone git://github.com/CarlosGabaldon/my_way.git

10 thoughts on “Singing with Sinatra

  1. You can clean up you haml code a lot by using the built in classes. Ie you can replace:

    %span{:class => “article_by”}

    with

    span.article_by

  2. Thanks hope it is helpful. I have not been following the recent changes in Sinatra, but I still think this should be a good starting point.

  3. This is the first hit when googling “sinatra orm”. Unfortunately since everything web and ruby needs to change its fundamental working bi-weekly, the howto here crumbles to dust from the “Install gems” section on downwards.

    Would be nice to link to some fresher howto from the top of this article (which of course will also work just this week and next…).

    Thanks for your intro anyway!
    *t

Leave a comment