Session originally given at Scotch on the Rocks 2013 in Edinburgh.
In this session we will explore how to build a RESTful-based application using the Sinatra framework, built on top of the Ruby programming language. We will explore installing Ruby, creating our first Sinatra application, the use of route definitions to handle multiple METHOD request types, including GET and POST requests, data persistence in a SQLite database, and how to return data in multiple formats including JSON and HTML. The RESTful approach and ease of use offered by Sinatra make it a great choice for underlying API requests which you can implement and call from any programming language of your choice.
5. WHAT WE’LL COVER:
Getting your first Sinatra project up and running
What is Sinatra?
Brief overview of a Sinatra application structure
Handling routes and formats
Deploying to Heroku *
* other options do exist
16. WHY NOT RAILS?
It is awesome though...
Rails is quite large and sometimes too much
monkeh$ rails create [stuff]
Ships with ORM, routes, test suites etc
Easily installed with RubyInstaller(.org)
17. SO WHY SINATRA?
Doesn’t force any framework
Extremely lightweight
A Domain Specific Language (DSL)
Incredibly quick and simple
(but plays well with others if you want to)
18. SO WHY SINATRA?
Runs on Rack
Can be a single file web application
Can use a number of JavaScript libraries
Can use a number of template engines
It also means I can show pictures like this...
22. SINATRA IS GREAT FOR
REST APIs
Small apps
useful for AJAX calls to data
Keeping your simple application simple
If it gets too big, consider using Rails (or CF)
25. monkeh$ ruby application.rb
== Sinatra/1.4.2 has taken to the stage on 4567 for development with
backup from Thin
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on localhost:4567, CTRL+C to stop
RUNNING THE APP
35. get '/hi' do
"Hello World!"
end
ROUTE BLOCKS
<--- this is a route block
A route block is an HTTP method paired with
a URL-matching pattern.
Processing takes place within the block (database calls etc)
and the result is sent to the browser.
37. SINATRA CAN DO THAT!
Caching
Multiple Routes
Content Types
All HTTP verbs supported
Good times!
38. REST
get '/' do
.. show something ..
end
post '/' do
.. create something ..
end
put '/' do
.. replace something ..
end
patch '/' do
.. modify something ..
end
delete '/' do
.. annihilate something ..
end
options '/' do
.. appease something ..
end
link '/' do
.. affiliate something ..
end
unlink '/' do
.. separate something ..
end
39. ROUTES
get '/' do
"Home page"
end
get '/hello' do
"Hello!"
end
get '/hello/:name' do
"Hello! #{params[:name]}"
end
40. ROUTES
get '/say/*/to/*' do
# matches /say/hello/to/world
params[:splat] # => ["hello", "world"]
end
get '/download/*.*' do
# matches /download/path/to/file.xml
params[:splat] # => ["hello", "world"]
end
get '/download*.*' do |path, ext|
# matches /download/path/to/file.xml
[path, ext] # => ["path/to/file", "xml"]
end
41. ROUTES
get %r{/hello/([w]+)} do
"Hello, #{params[:captures].first}!"
end
get '/posts.?:format?' do
# matches "/posts " and any extension
# eg "GET /posts.xml" or "GET /posts.json"
end
42. ROUTES
get '/hi', :agent => /Mozilla/(d.d)sw?/ do
"You’re using Mozilla version #{params[:agent][0]}"
end
get '/hi' do
# matches all non-Mozilla browsers
end
53. SINGLE PAGE APP
application.rb
require 'rubygems'
require 'sinatra'
get '/' do
html = '<form method="post">'
html += '<input type="text" placeholder="Add the URL to shorten here..."'
html += 'name="url" id="url" />'
html += '<input type="submit" value="Shorten!" />'
html += '</form>'
html
end
post '/' do
html = '<p>Thanks for submitting a URL.</p>'
html
end
55. USING VIEWS
application.rb
require 'rubygems'
require 'sinatra'
get '/' do
erb :index
end
post '/' do
erb :index
end
views/index.erb
<form method="post">
<input type="text" placeholder="Add the URL to shorten here..."
name="url" id="url" />
<input type="submit" value="Shorten!" />
</form>
57. LAYOUTS
A template that calls yield to draw in view data
Can also be managed through route blocks
A template called “layout” will be used by default
65. FILTERS
application.rb
# This code will run before each event
# Very useful for debugging parameters sent via the console
before do
puts '[Params]'
p params
end
# This code will run after each event
after do
puts response.status
end
66. CONFIGURATION
Will run once at startup
Can be used to
set application-wide values and options
perform certain processes per environment
67. CONFIGURATION
configure do
# All environments
end
configure :production do
# Production only
end
configure :development, :test do
# Development and Test
end
68. CONFIGURATION
configure do
set :variable, 'foo'
# multiple options
set :variable1 => 'Hello', :variable2 => 'world'
# same as set :option, true
enable :option
# same as set :option, false
disable :option
end
get '/' do
settings.variable? # => true
settings.variable # => 'foo'
end
71. DATAMAPPER
Same API can talk to multiple datastores
Uses adapters to achieve this
sqlite
mysql
postgresql
72. DATAMAPPER
Define mappings in your model
Comes bundled with tools to assist with
migration
constraints
transactions
timestamps
validations
...and more!
77. GETTING DATA
application.rb
# root page
get '/' do
# get the current object of all links stored
@urls = ShortURL.all;
erb :index
end
views/index.erb
<h1>Serving <%= @urls.count %> links</h1>
80. CONTENT TYPES
Return content in a number of formats, including
JSON
XML
HTML
monkeh$ gem install json
May need some more gems to process
81. CONTENT TYPE
application.rb
get '/' do
if params[:url] and not params[:url].empty?
@shortURL = generate_short_url(params[:url])
content_type :json
{
:original_url => params[:url],
:short_url => @shortURL
}.to_json
else
# get the current count of all links stored
@urls = ShortURL.all;
erb :index
end
end
86. TESTING WITH RSPEC
spec/application_rspec.rb
require_relative '../application.rb'
require 'rack/test'
set :environment, :test
def app
Sinatra::Application
end
describe 'URL Shortening Service' do
include Rack::Test::Methods
it "should load the home page" do
get '/'
last_response.should be_ok
end
end
98. monkeh$ heroku create urlshrinkapp
Creating urlshrinkapp... done, stack is cedar
http://urlshrinkapp.herokuapp.com/ | git@heroku.com:urlshrinkapp.git
Git remote heroku added
HEROKU
99. monkeh$ git push heroku master
Total 19 (delta 2), reused 0 (delta 0)
-----> Ruby/Rack app detected
-----> Installing dependencies using Bundler version 1.3.2
Running: bundle install --without development:test --path vendor/bundle --binstubs
vendor/bundle/bin --deployment
Fetching gem metadata from https://rubygems.org/.........
Fetching gem metadata from https://rubygems.org/..
Installing data_objects (0.10.13)
Installing dm-core (1.2.0)
Installing dm-do-adapter (1.2.0)
Installing dm-migrations (1.2.0)
Installing do_postgres (0.10.13)
Installing dm-postgres-adapter (1.2.0)
Installing eventmachine (1.0.3)
Installing json (1.8.0)
Installing rack (1.5.2)
Installing rack-protection (1.5.0)
Installing tilt (1.4.1)
Installing sinatra (1.4.2)
Using bundler (1.3.2)
Your bundle is complete! It was installed into ./vendor/bundle
Cleaning up the bundler cache.
-----> Discovering process types
Procfile declares types -> web
Default types for Ruby/Rack -> console, rake
-----> Compiled slug size: 3.0MB
-----> Launching... done, v4
http://urlshrinkapp.herokuapp.com deployed to Heroku
To git@heroku.com:urlshrinkapp.git
* [new branch] master -> master
HEROKU
100. monkeh$ heroku addons:add heroku-postgresql:dev
Adding heroku-postgresql:dev on urlshrinkapp... done, v5 (free)
Attached as HEROKU_POSTGRESQL_BROWN_URL
Database has been created and is available
heroku pg:promote HEROKU_POSTGRESQL_BROWN_URL
Promoting HEROKU_POSTGRESQL_BROWN_URL to DATABASE_URL... done
HEROKU