Bets placed by Sinatra

In my last article I wrote about the cool Ruby DSL web framework called Sinatra which is taking the Ruby world by storm. I decided that another “How to” article on some of Sintra’s other kick ass features was just what Frank would expect.

Here is what I will be covering in this article:

  • Sinatra Restful support
  • Sequel
  • SqlLite3
  • Haml
  • Rake

Let’s first start by getting everything installed. I will be using OS 10.5.7, Terminal, and TextMate for this article.

You’ll need to launch the Terminal application. It can be found in:

/Applications/Utilities

Each of the lines below appearing in monospaced type should be entered into Terminal, and be followed by the Return key.

Ruby

curl -O ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p72.tar.gz
tar xzvf ruby-1.8.7-p72.tar.gz
cd ruby-1.8.7-p72
./configure --enable-shared --enable-pthread CFLAGS=-D_XOPEN_SOURCE=1
make
sudo make install
cd ..

Verify that Ruby is installed and in your path, just type:

which ruby

If everything installed successfully you will see:

/usr/local/bin/ruby

RubyGems

With Ruby installed, we can move on to RubyGems. Same routine:

curl -O http://rubyforge.iasi.roedu.net/files/rubygems/rubygems-1.3.1.tgz
tar xzvf rubygems-1.3.1.tgz
cd rubygems-1.3.1
sudo /usr/local/bin/ruby setup.rb
cd ..

Sqlite3

Next let’s install sqlite3:

curl -O 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

Gems

Lastly, the gems:

sudo gem install sinatra sqlite3-ruby sequel haml

Bookie

When you are rolling with Sinatra you always need to have a bookie around..

So let’s start by creating the project structure, but let’s have a little fun and use Rake do all the boring stuff for us.

First we need to create our bookie directory:

mkdir bookie
cd ./bookie
mate rakefile

Add the following to your rake file:

alias :original_directory :directory
def directory(dir)
  original_directory dir
  Rake::Task[dir]
end

namespace :bookie do
  task :create_dir => [
    directory('db2'),
    directory('public2'),
    directory('views2'),
  ]
  
  desc "Create the bookie project."
  task :create_project => :create_dir do
    view_files = ["edit.haml","error.haml","layout.haml", 
            "list.haml", "not_found.haml", "view.haml"]
            
    rb_files = ["bookmark2.rb", "bookmarks2.rb"]
    
    puts "Creating the bookie project."
    view_files.each do |f|
      puts "Creating view file: #{f}"
      `touch ./views2/#{f}`
    end
    rb_files.each do |f|
      puts "Creating ruby file: #{f}"
      `touch #{f}`
    end
  end
end

Then save the file and from terminal run the following rake command:

rake bookie:create_project

Now we have a cool little rake file that can create our bookie project structure. Now we can start hacking.

Sinatra supports all the HTTP verbs that make a true RESTful web application. Let’s start by opening up the bookmarks.rb file and stub out our action methods.

require 'rubygems'
require 'sinatra'

#list bookmarks
get '/' do
end

#view a bookmark
get '/bookmarks/:id' do |id|
end

#edit a bookmark
get '/bookmarks/:id/edit' do |id|
end

#create a bookmark
post '/bookmarks/' do
end

#update a bookmark
put '/bookmarks/' do
end

#delete a bookmark
delete '/bookmarks/' do 
end

In Sinatra a route is an HTTP method paired with an URL matching pattern. Each route is associated with a block, so with the route:

get '/bookmarks/:id' do |id|
end

What is happening here is we are passing to the Sinatra get method the pattern “/bookmarks/:id” and a block. Which means that when Sinatra receives a HTTP GET request that matches against the pattern, say, “/bookmarks/1” it will invoke our associated block passing it the id of 1.

Let’s make sure that everything is working before we go any further. Edit the get ‘/’ route to look like the following

get '/' do
  "Place your bets.."
end

If you open another terminal window and run the following:

ruby bookmarks.rb 

Then if you open a browser:

open http://0.0.0.0:4567/

You should see the following:
place your bets

Now that we verified that everything is running, let’s talk about our models.

Models

To keep things simple we are going to use Sqlite3 as our database and use Sequel as our database mapper. Sequel is a lightweight database access toolkit for Ruby. It provides thread safety, connection pooling and a concise DSL for constructing database queries and table schemas. Sequel also includes a lightweight but comprehensive ORM layer for mapping records to Ruby objects and handling associated records. Sequel supports advanced database features such as prepared statements, bound variables, stored procedures, master/slave configurations, and database sharding. Sequel makes it easy to deal with multiple records without having to break your teeth on SQL.

To start open up bookmark.rb and add the following:

DB = Sequel.sqlite('./db/booky.db')

unless DB.table_exists? :book_marks
  DB.create_table :book_marks do
    primary_key :id
    varchar :title
    varchar :link
  end
  
  # populate the table
  DB[:book_marks].insert(:title => 'GitHub', :link => "http://github.com" )
  DB[:book_marks].insert(:title => 'Yahoo', :link => "http://yahoo.com")
  DB[:book_marks].insert(:title => 'Google', :link => "http://google.com")
  
end

class BookMark < Sequel::Model
end

Sequel is a very powerful and impressive database access toolkit that deserves an entire blog article devoted to exploring it’s features. The website provides a wealth of information that will get you up to speed on all the cool features of Sequel.

Now that we have a our database layer setup let’s go back to our controller to start adding some functionality. Open bookmarks.rb again and edit to match the following:

require 'rubygems'
require 'sinatra'
require 'sequel'
require 'bookmark'


before do
  @message = "Bookmarks"
  @book_marks = BookMark.all
end

#list bookmarks
get '/' do
  haml :list
end

Then open layout.haml and add the following:

%html
  %head
    %title Bookie - keep'n you links straight
  %body
    #container
      = yield

Then open list.haml:

#content
  %h2 
    == Your #{@book_marks.length} bookmarks
  %ol
    -@book_marks.each do |b|
      %li
        %label Bookmark:
        %a{:href => "#{b.link}"} 
          = b.title
        %form{:method => "post", :action => "/bookmarks/"}
          %input{:type => "hidden", :name => "_method", :value => "delete"}
          %input{:type => "hidden", :name => "id", :value => "#{b.id}"}
          %a{:href => "/bookmarks/#{b.id}"}
            View
          %label or
          %input{:type => "submit", :value => "Delete"}

  %h2 New Bookmark
  %form{:method => "post", :action => "/bookmarks/"}
    %label Title:
    %input{:type => "text", :id => "title", :name => "title", :value => ""}
    %br
    %label URL:
    %input{:type => "text", :id => "link", :name => "link", :value => ""}
    %br
    %input{:type => "submit", :value => "Add"}

If you refresh your browser you should see the following:

bookie-list

Now that we are getting somewhere let’s finish up the controller. Modify bookmarks.rb to match the following:

require 'rubygems'
require 'sinatra'
require 'sequel'
require 'bookmark'


before do
  @message = "Bookmarks"
  @book_marks = BookMark.all
end

#list bookmarks
get '/' do
  haml :list
end

#view a bookmark
get '/bookmarks/:id' do |id|
  @book_mark = BookMark[id]
  haml :view
end

#edit a bookmark
get '/bookmarks/:id/edit' do |id|
  @book_mark = BookMark[id]
  haml :edit
  
end

#create a bookmark
post '/bookmarks/' do
  title = params[:title]
  link = params[:link]
  
  unless title == "" && link == ""
    BookMark.new do |b|
      b.title = title
      b.link = link
      b.save
    end
  end
  
  redirect '/'
end

#update a bookmark
put '/bookmarks/' do
  id = params[:id]
  unless id == ""
    title = params[:title]
    link = params[:link]
  
    unless title == "" && link == ""
      b = BookMark[id]
      b.title = title
      b.link = link
      b.save
    end
  end
  
  redirect '/' if id == ""
  redirect "/bookmarks/#{id}"
  
end

#delete a bookmark
delete '/bookmarks/' do 
  id = params[:id]
  b = BookMark[id]
  b.destroy
  redirect '/'
end

Let’s finish up our views:

edit.haml


%form{:method => "post", :action => "/bookmarks/"}
  %input{:type => "hidden", :name => "_method", :value => "put"}
  %input{:type => "hidden", :name => "id", :id => "id", :value =>"#{@book_mark.id}" }
  %label Title:
  %input{:type => "text", :name => "title", :id => "title", :value =>"#{@book_mark.title}" }
  %br
  %label URL:
  %input{:type => "text", :name => "link", :id => "link", :value => "#{@book_mark.link}"}
  %br
  %input{:type => "submit", :value => "Update"}

%a{:href => "/bookmarks/#{@book_mark.id}"}
  << Back

view.haml

%label Bookmark:
%a{:href => "#{@book_mark.link}"} 
  = @book_mark.title
%form{:method => "post", :action => "/bookmarks/"}
  %input{:type => "hidden", :name => "_method", :value => "delete"}
  %input{:type => "submit", :value => "Delete"}
  %label  or
  %a{:href => "/bookmarks/#{@book_mark.id}/edit"}
    Edit 

%a{:href => '/'}
  << Back

Well that is our basic Bookie application which highlights the power and simplicity of Sinatra. As with all projects that I work on you can use git to get yourself a copy with the simple command:

git clone git://github.com/CarlosGabaldon/bookie.git

Follow the bookie repo to see what other cool features get added by me and others.

3 thoughts on “Bets placed by Sinatra

  1. Pingback: Carlos Gabaldon
  2. I kind of like this Sinatra stuff. I have almost done all my programming in PHP. But now I have the time to look into ruby. After reading some very good tutorials on ruby I started looking at Ruby On Rails and got a book (900 pages or so). Ruby seems like a cool language and it also seems easy to learn. The problem is just – for a newbie to a big framework like rails – that you get lost – it is not the best starting point I guess (not for me at least) With this Sinatra stuff it seems much easier to investigate a couple of classes at a time without getting too confused, and find out what actually goes on. Nice writing :)

Leave a comment