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:
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:
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.
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 :)
Good stuff. Helped me during initial stages of learning sinatra. Thanks a lot :)