Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort:
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
Install the gems needed:
gem install sinatra rackup puma
And run with:
ruby myapp.rb
View at: http://localhost:4567
The code you changed will not take effect until you restart the server. Please restart the server every time you change or use a code reloader like rerun or rack-unreloader.
It is recommended to also run gem install puma
, which Sinatra will
pick up if available.
yield
and nested layoutsIn Sinatra, a route is an HTTP method paired with a URL-matching pattern. Each route is associated with a block:
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
Routes are matched in the order they are defined. The first route that matches the request is invoked.
Routes with trailing slashes are different from the ones without:
get '/foo' do
# Does not match "GET /foo/"
end
Route patterns may include named parameters, accessible via the
params
hash:
get '/hello/:name' do
# matches "GET /hello/foo" and "GET /hello/bar"
# params['name'] is 'foo' or 'bar'
"Hello #{params['name']}!"
end
You can also access named parameters via block parameters:
get '/hello/:name' do |n|
# matches "GET /hello/foo" and "GET /hello/bar"
# params['name'] is 'foo' or 'bar'
# n stores params['name']
"Hello #{n}!"
end
Route patterns may also include splat (or wildcard) parameters, accessible
via the params['splat']
array:
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'] # => ["path/to/file", "xml"]
end
Or with block parameters:
get '/download/*.*' do |path, ext|
[path, ext] # => ["path/to/file", "xml"]
end
Route matching with Regular Expressions:
get //hello/([w]+)/ do
"Hello, #{params['captures'].first}!"
end
Or with a block parameter:
get %r{/hello/([w]+)} do |c|
# Matches "GET /meta/hello/world", "GET /hello/world/1234" etc.
"Hello, #{c}!"
end
Route patterns may have optional parameters:
get '/posts/:format?' do
# matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc
end
Routes may also utilize query parameters:
get '/posts' do
# matches "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# uses title and author variables; query is optional to the /posts route
end
By the way, unless you disable the path traversal attack protection (see below), the request path might be modified before matching against your routes.
You may customize the Mustermann
options used for a given route by passing in a :mustermann_opts
hash:
get 'A/postsz', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# matches /posts exactly, with explicit anchoring
"If you match an anchored pattern clap your hands!"
end
It looks like a condition, but it isn't one! These options will
be merged into the global :mustermann_opts
hash described
below.
Routes may include a variety of matching conditions, such as the user agent:
get '/foo', :agent => /Songbird (d.d)[d/]*?/ do
"You're using Songbird version #{params['agent'][0]}"
end
get '/foo' do
# Matches non-songbird browsers
end
Other available conditions are host_name
and provides
:
get '/', :host_name => /^admin./ do
"Admin Area, Access denied!"
end
get '/', :provides => 'html' do
haml :index
end
get '/', :provides => ['rss', 'atom', 'xml'] do
builder :feed
end
provides
searches the request's Accept header.
You can easily define your own conditions:
set(:probability) { |value| condition { rand <= value } }
get '/win_a_car', :probability => 0.1 do
"You won!"
end
get '/win_a_car' do
"Sorry, you lost."
end
For a condition that takes multiple values use a splat:
set(:auth) do |*roles| # <- notice the splat here
condition do
unless logged_in? && roles.any? {|role| current_user.in_role? role }
redirect "/login/", 303
end
end
end
get "/my/account/", :auth => [:user, :admin] do
"Your Account Details"
end
get "/only/admin/", :auth => :admin do
"Only admins are allowed here!"
end
The return value of a route block determines at least the response body passed on to the HTTP client or at least the next middleware in the Rack stack. Most commonly, this is a string, as in the above examples. But other values are also accepted.
You can return an object that would either be a valid Rack response, Rack body object or HTTP status code:
[status (Integer), headers (Hash), response body (responds to #each)]
[status (Integer), response body (responds to #each)]
#each
and passes nothing but strings to
the given blockThat way we can, for instance, easily implement a streaming example:
class Stream
def each
100.times { |i| yield "#{i}n" }
end
end
get('/') { Stream.new }
You can also use the stream
helper method (described below) to reduce
boilerplate and embed the streaming logic in the route.
As shown above, Sinatra ships with built-in support for using String patterns and regular expressions as route matches. However, it does not stop there. You can easily define your own matchers:
class AllButPattern
def initialize(except)
@except = except
end
def to_pattern(options)
return self
end
def params(route)
return {} unless @except === route
end
end
def all_but(pattern)
AllButPattern.new(pattern)
end
get all_but("/index") do
# ...
end
Note that the above example might be over-engineered, as it can also be expressed as:
get /.*/ do
pass if request.path_info == "/index"
# ...
end
Static files are served from the ./public
directory. You can specify
a different location by setting the :public_folder
option:
set :public_folder, __dir__ + '/static'
Note that the public directory name is not included in the URL. A file
./public/css/style.css
is made available as
http://example.com/css/style.css
.
Use the :static_cache_control
setting (see below) to add
Cache-Control
header info.
Each template language is exposed via its own rendering method. These methods simply return a string:
get '/' do
erb :index
end
This renders views/index.erb
.
Instead of a template name, you can also just pass in the template content directly:
get '/' do
code = "<%= Time.now %>"
erb code
end
Templates take a second argument, the options hash:
get '/' do
erb :index, :layout => :post
end
This will render views/index.erb
embedded in the
views/post.erb
(default is views/layout.erb
, if it exists).
Any options not understood by Sinatra will be passed on to the template engine:
get '/' do
haml :index, :format => :html5
end
You can also set options per template language in general:
set :haml, :format => :html5
get '/' do
haml :index
end
Options passed to the render method override options set via set
.
Available Options:
Templates are assumed to be located directly under the ./views
directory. To use a different views directory:
set :views, settings.root + '/templates'
One important thing to remember is that you always have to reference
templates with symbols, even if they're in a subdirectory (in this case,
use: :'subdir/template'
or 'subdir/template'.to_sym
). You must use a
symbol because otherwise rendering methods will render any strings
passed to them directly.
get '/' do
haml '%div.title Hello World'
end
Renders the template string. You can optionally specify :path
and
:line
for a clearer backtrace if there is a filesystem path or line
associated with that string:
get '/' do
haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3
end
Some languages have multiple implementations. To specify what implementation to use (and to be thread-safe), you should simply require it first:
require 'rdiscount'
get('/') { markdown :index }
Dependency | haml |
File Extension | .haml |
Example | haml :index, :format => :html5 |
Dependency | erubi or erb (included in Ruby) |
File Extensions | .erb, .rhtml or .erubi (Erubi only) |
Example | erb :index |
Dependency | builder |
File Extension | .builder |
Example | builder { |xml| xml.em "hi" } |
It also takes a block for inline templates (see example).
Dependency | nokogiri |
File Extension | .nokogiri |
Example | nokogiri { |xml| xml.em "hi" } |
It also takes a block for inline templates (see example).
Dependency | sass-embedded |
File Extension | .sass |
Example | sass :stylesheet, :style => :expanded |
Dependency | sass-embedded |
File Extension | .scss |
Example | scss :stylesheet, :style => :expanded |
Dependency | liquid |
File Extension | .liquid |
Example | liquid :index, :locals => { :key => 'value' } |
Since you cannot call Ruby methods (except for yield
) from a Liquid
template, you almost always want to pass locals to it.
Dependency | Anyone of: RDiscount, RedCarpet, kramdown, commonmarker pandoc |
File Extensions | .markdown, .mkd and .md |
Example | markdown :index, :layout_engine => :erb |
It is not possible to call methods from Markdown, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine:
erb :overview, :locals => { :text => markdown(:introduction) }
Note that you may also call the markdown
method from within other
templates:
%h1 Hello From Haml!
%p= markdown(:greetings)
Since you cannot call Ruby from Markdown, you cannot use layouts written in
Markdown. However, it is possible to use another rendering engine for the
template than for the layout by passing the :layout_engine
option.
Dependency | RDoc |
File Extension | .rdoc |
Example | rdoc :README, :layout_engine => :erb |
It is not possible to call methods from RDoc, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine:
erb :overview, :locals => { :text => rdoc(:introduction) }
Note that you may also call the rdoc
method from within other templates:
%h1 Hello From Haml!
%p= rdoc(:greetings)
Since you cannot call Ruby from RDoc, you cannot use layouts written in
RDoc. However, it is possible to use another rendering engine for the
template than for the layout by passing the :layout_engine
option.
Dependency | Asciidoctor |
File Extension | .asciidoc, .adoc and .ad |
Example | asciidoc :README, :layout_engine => :erb |
Since you cannot call Ruby methods directly from an AsciiDoc template, you almost always want to pass locals to it.
Dependency | Markaby |
File Extension | .mab |
Example | markaby { h1 "Welcome!" } |
It also takes a block for inline templates (see example).
Dependency | Rabl |
File Extension | .rabl |
Example | rabl :index |
Dependency | Slim Lang |
File Extension | .slim |
Example | slim :index |
Dependency | yajl-ruby |
File Extension | .yajl |
Example | yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource' |
The template source is evaluated as a Ruby string, and the
resulting json variable is converted using #to_json
:
json = { :foo => 'bar' }
json[:baz] = key
The :callback
and :variable
options can be used to decorate the rendered
object:
var resource = {"foo":"bar","baz":"qux"};
present(resource);
Templates are evaluated within the same context as route handlers. Instance variables set in route handlers are directly accessible by templates:
get '/:id' do
@foo = Foo.find(params['id'])
haml '%h1= @foo.name'
end
Or, specify an explicit Hash of local variables:
get '/:id' do
foo = Foo.find(params['id'])
haml '%h1= bar.name', :locals => { :bar => foo }
end
This is typically used when rendering templates as partials from within other templates.
yield
and nested layoutsA layout is usually just a template that calls yield
.
Such a template can be used either through the :template
option as
described above, or it can be rendered with a block as follows:
erb :post, :layout => false do
erb :index
end
This code is mostly equivalent to erb :index, :layout => :post
.
Passing blocks to rendering methods is most useful for creating nested layouts:
erb :main_layout, :layout => false do
erb :admin_layout do
erb :user
end
end
This can also be done in fewer lines of code with:
erb :admin_layout, :layout => :main_layout do
erb :user
end
Currently, the following rendering methods accept a block: erb
, haml
,
liquid
, slim
. Also, the general render
method accepts a block.
Templates may be defined at the end of the source file:
require 'sinatra'
get '/' do
haml :index
end
__END__
@@ layout
%html
!= yield
@@ index
%div.title Hello world.
NOTE: Inline templates defined in the source file that requires Sinatra are
automatically loaded. Call enable :inline_templates
explicitly if you
have inline templates in other source files.
Templates may also be defined using the top-level template
method:
template :layout do
"%htmln =yieldn"
end
template :index do
'%div.title Hello World!'
end
get '/' do
haml :index
end
If a template named "layout" exists, it will be used each time a template
is rendered. You can individually disable layouts by passing
:layout => false
or disable them by default via
set :haml, :layout => false
:
get '/' do
haml :index, :layout => !request.xhr?
end
To associate a file extension with a template engine, use
Tilt.register
. For instance, if you like to use the file extension
tt
for Haml templates, you can do the following:
Tilt.register Tilt[:haml], :tt
First, register your engine with Tilt, then create a rendering method:
Tilt.register MyAwesomeTemplateEngine, :myat
helpers do
def myat(*args) render(:myat, *args) end
end
get '/' do
myat :index
end
Renders ./views/index.myat
. Learn more about
Tilt.
To implement your own template lookup mechanism you can write your
own #find_template
method:
configure do
set :views, [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
Before filters are evaluated before each request within the same context as the routes will be and can modify the request and response. Instance variables set in filters are accessible by routes and templates:
before do
@note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
@note #=> 'Hi!'
params['splat'] #=> 'bar/baz'
end
After filters are evaluated after each request within the same context as the routes will be and can also modify the request and response. Instance variables set in before filters and routes are accessible by after filters:
after do
puts response.status
end
Note: Unless you use the body
method rather than just returning a
String from the routes, the body will not yet be available in the after
filter, since it is generated later on.
Filters optionally take a pattern, causing them to be evaluated only if the request path matches that pattern:
before '/protected/*' do
authenticate!
end
after '/create/:slug' do |slug|
session[:last_slug] = slug
end
Like routes, filters also take conditions:
before :agent => /Songbird/ do
# ...
end
after '/blog/*', :host_name => 'example.com' do
# ...
end
Use the top-level helpers
method to define helper methods for use in
route handlers and templates: