For those of you who don't know, Sinatra the newest kid on the open-source web frameworks has been released late last week.
This super-sexy DSL runs at lighting speed. It sits on top of Mongrel and was written to be thread-safe, sleek and tiny. And an entire web-application can be written and contained in one file (or a small collection of files)! Think Facebook apps...
Guess what else? It's super easy to use! Let's create an app from scratch to demonstrate!
gem install sinatra -y
That's it! It's a gem! Of course, you need to have ruby installed.
I need a new blog. My old one is just getting too cumbersome and, well.. it's not written in ruby. So, let's make one. I want it to be restful and simple. Sinatra is the perfect framework for this task.
require "rubygems"
require "sinatra"
get '/' do
"And the blogging begins"
end
Guess what... That's all we need for the basis of our application. It doesn't do much, but it'll get us started. So, save that bad boy into a file called "blog.rb" and whip out your favorite terminal and type
ruby "blog.rb" # == Sinatra has taken the stage on port 4567!
Now, check out what you see in your browser and pat yourself on the back. You've built the first page to the blog. Super slick! In 6 lines of code, you just built yourself a dynamic webpage.
Press control+c to quit the app
Sinatra makes no assumptions as to what you are going to use for a ORM, so if you want you can use ActiveRecord, DBI or, IMHO a super-flexible Sequel
In this app, we will use sqlite3 and sequel in conjunction so I'll add
require 'sequel/sqlite'
And be off
To get us up and running on a database, you should download the vendor directory attached with this post and place it in root of your application. You should have a blog.rb and a vendor directory.
Sinatra will auto-load files in your vendor directory with an init.rb
Plugins (features) are out of the scope of this tutorial, but stay tuned, I'll show you how to dive head-first into creating your own features (also known as plugins) in an upcoming one.
Using the database feature, we can setup_tables and teardown_tables and define our connection to the database. To define your own connection, you can use the connection method like so:
connection('sqlite:///blog.db')
If you don't call the connection method, the plugin (feature) assumes you will be using the database defined as sqlite:///blog.db
Setting up the tables is syntacticly similar to what you might think:
setup_tables do
create_table :posts do
primary_key :id
varchar :title, :size => 255, :unique => true
timestamp :created_at
text :body
end
end
This will run the setup_tables when you start the application and that gives us a posts table. Simple as pie.
You can also call teardown_tables and it will reset your tables for you when you close the application. Both of these methods allow you to specify if you want them to run with a second parameter, like so:
teardown_tables false do
execute 'DROP TABLE posts'
end
Now we are off and rolling and can use the DB freely from within the application. Let's write a helper real quick to handle our posts.
helpers do
def posts
connection[:posts]
end
end
In Sinatra, we define our helpers in the helper block. Then they are accessible throughout the application. This method allows us to call posts and get the dataset of posts (Sequel).
get '/' do
body do
if posts.empty?
"There are no posts<br />" + new_post_link
else
"<ul>"+posts.all.collect {|post| "<li>"+post[:title]+" -
"+post[:body]+"</li>"}+"</ul><br />"+new_post_link
end
end
end
Notice we didn't define any routes. Based on the context of the request, Sinatra will route it appropriately after you define the action. For instance
get '/hello_world' do
end
will route any get request pointed at /hello_world to that action.
So we have a fairly rough index page for the blog, but it's enough for the time being. Notice that we added a link to add a new post and called it new_post_link. That's actually a helper, so let's go add that above.
helpers do
def new_post
"<a href='/new'>Add</a> a new post"
end
...
end
One interesting feature you may have missed is the body method. All the output in Sinatra is handled through the body. We can force the output by using the body method like so:
body "Sinatra rules"
body do
"Sinatra rules even more"
end
Let's add a method to add a new post.
get '/new' do
body do
<<-eos
<h3>Add a new post</h3>
<form action='/create' method='post'>
<label for='title'>Title</label><br />
<input type='text' name='title' /><br />
<label for='body'>Body</label><br />
<textarea name='body'></textarea><br />
<input type='submit' name='submit' value='Add' />
</form>
eos
end
end
Adding the form, we have a method for the user to create a post that points to a post create method that we are about to create....
right about...
post '/create' do
title, text = params[:title], params[:body]
posts.insert(:title => title, :body => text)
redirect '/'
end
Just as you'd expect. We expect to have a post here at the enpoint '/create'. Sequel makes it easy to insert and we have a redirect helper provided by Sinatra.
Lets add a method to edit the posts. First, lets add an edit link to the list of posts.
get '/' do
body do
if posts.empty?
"There are no posts<br />"+new_post_link
else
"<ul>"+posts.all.collect {|post| "<li>"+edit_post_link(post[:title])+" -
<a href='"+post[:id]+"'>
"+post[:title]+"</a> - "+post[:body]+"</li>"}+"</ul><br />"+new_post_link
end
end
end
Now, of course we need a way to actually look at the posts, so lets add a get action for each of the posts
get '/:id' do
post = posts[:id => params[:id].to_i]
body do
<<-eos
<h3>"+post[:title]+"</h3>
<p>"+post[:body]+"</p>
<p><a href="/">home</p></p>
eos
end
end
This way we can fetch the post based off the id and display it nicely to the user. Not done yet, but almost!
We also need a way to edit the actual data, so let's show the user that form
get '/edit/:id' do
post = posts[:id => params[:id].to_i]
id, title, body = post[:id], post[:title], post[:body]
body do
<<-eos
<h3>Edit post</h3>
<form action='/"+title+"' method='post'>
<label for='title'>Title</label><br />
<input type='text' name='title' value="+title+" /><br />
<label for='body'>Body</label><br />
<textarea name='body'>"+body+"</textarea><br />
<input type='hidden' name='_method' value='PUT' />
<input type='submit' name='submit' value='Update' />
</form>
eos
end
end
One thing to note here is that we are given pretty urls to work with. Super-sleek! One other thing to note is that the form contains a special parameters _method This is how Sinatra fakes both of the urls PUT and DELETE as those are not standard across browsers. Ruby on rails does the same thing, if you are familiar with it. Another note is that this only works when you POST to the url. Otherwise, Sinatra just assumes you are submitting a GET request.
Also note that there is both a get and a put request to the same '/:id' endpoint. As long as they are under different actions, you can have multiple actions for the same endpoint. This is a feature of RESTful development, something Sinatra is very familiar with.
put '/:id' do
title, text = params[:title], params[:body]
if posts.filter(:id => params[:id].to_i).update(:title => title, :body => text)
body "Edited successfully: <a href='/'>Home</a>"
else
body "There was an error"
end
end
And there we go. That completes the first-level the Sinatra blog, part 1.
Don't forget to catch the files
That's it? What's next?
In
part 2
(not ready for consumption yet) we'll look at ways to refactor this code and see some more advanced features of Sinatra.
Part 3
we will do some feature (plugin) development
Go Home