What's New in Sinatra 1.2?
As announced on the mailing list, we have just released Sinatra 1.2.0. Let’s have a closer look at the new features.
Slim support
Sinatra now supports the Slim Template Engine:
require 'sinatra'
require 'slim'
get('/') { slim :index }
__END__
@@ index
! doctype html
html
head
title Sinatra With Slim
body
h1 Slim Is Fun!
a href="https://haml-lang.com/" A bit like Haml, don't you think?
Inline Markaby
Like Builder and Nokogiri templates, Markaby can now be used directly inline:
require 'sinatra'
require 'markaby'
get '/' do
markaby do
html do
head { title "Sinatra With Markaby" }
body { h1 "Markaby Is Fun!" }
end
end
end
Layout Engines
Whenever you render a template, like some_page.haml
, Sinatra will use a
corresponding layout, like layout.haml
for you. With the :layout
option it
has always been possible to use a different layout, but it still had to be
written in the same template language, Haml in our example. In 1.1 we introduced
markdown
, textile
and rdoc
templates. It is not possible to use those for
layouts. We therefore added the :layout_engine
option, which easily allows you
to combine one two different template engines:
require 'sinatra'
require 'rdiscount'
# for all markdown files, use post.haml as layout
set :markdown, :layout_engine => :haml, :layout => :post
get '/' do
# use index.haml for readme
markdown :README, :layout => :index
end
get '/:post' do
markdown params[:post].to_sym
end
This feature should also be handy when migrating from one template language to another, as it allows you to combine Erb with Haml, for instance.
Conditional Filters
We introduced pattern matching filters in 1.1. Now they also support conditions:
before :agent => /Song Bird/ do
# ...
end
Those can also be combined with patterns, of course:
after '/api/*', :provides => :json do
# ...
end
URL helper
Usually Sinatra does not provide any view helper methods. Those are provided by extensions and would not suit Sinatra’s approach of a small but robust core. However, constructing URLs is a use case most people run into sooner or later. It is a bit complicated to construct URLs right. Consider this example:
get('/foo') { "<a href='/bar'>Will you make it?</a>" }
get('/bar') { "You made it!" }
Feel free to run it. Works, doesn’t it? So, what is wrong with it?
Imagine your app is “mounted” by another Rack application, for instance in a
config.ru
like this:
map('/there') { run Sinatra::Application }
map('/') { run MyRailsApp::Application }
Now the link to /bar
would end up in a request send to MyRailsApp
rather
than to Sinatra. Injecting request.script_name
would fix this, but be
honest, how often do you do that?
Now, imagine these links are presented out of context, in an RSS feed or
embedded on another host. In that case you might want to construct absolute
URLs. This is even more cumbersome, as you most certainly either forget to
handle reverse proxies, alternative ports/protocols or you end up with lots of
URL related code all over the place, while what you should do is use the url
helper:
get('/foo') { "<a href='#{url '/bar'}'>You will make it!</a>" }
get('/bar') { "You made it!" }
Since you are likely going to use this with redirects, we also aliased the
helper to to
:
get('/foo') { redirect to('/bar') }
get('/bar') { "You made it!" }
Named Captures on 1.9
Ruby 1.9 introduced named captures
for regular expressions. Sinatra accepts regular expressions for matching paths.
Now named captures will automatically end up populating params
:
get %r{/(?<year>\d{4})/(?<month>\d{2})/(?<day>\d{2})/?} do
date = Date.new params[:year].to_i, params[:month].to_i, params[:day].to_i
@posts = Post.pubished_on date
erb :posts
end
Templates with different scopes
All rendering methods now accept a :scope
options:
get '/:id' do |id|
@post = Post.find id
# without scope
erb "<%= @post.name %>"
# with scope
erb "<%= name %>", :scope => @post
end
Note that all Sinatra helper methods and instance variables will not be available.
Configurable redirects
In 1.1 we made sure all redirects were absolute URIs, to conform with RFC 2616
(HTTP 1.1). This will result in issues for you if you have a broken Reverse
Proxy configuration. If so, you should really fix your configuration. If you are
unable to do so, a simple disable :absolute_redirects
will now give you back
the 1.0 behavior. As shown above, you can now use the to
helper with redirect.
If all your redirects are application local, you can now
enable :prefixed_redirects
and skip the to
altogether:
enable :prefixed_redirects
get('/foo') { redirect '/bar' }
get('/bar') { "You made it!" }
We did not enable this per default to not break compatibility and to allow you redirects to other Rack endpoints.
Overriding template lookup
One popular feature request is supporting multiple view folders. But everyone wants different semantics. So, instead of choosing one way to go, we gave you means to implement your own lookup logic:
helpers do
def find_template(*)
puts "looking for index.txt"
yield "views/index.txt"
puts "apparently, index.txt doesn't exist, let's try index.html"
yield "views/index.html"
end
end
get "/" do
haml :foo
end
Sinatra will call find_template
to discover the template file. In the above
example, we don’t care about what template engine to use or what name the
template has. It will use views/index.txt
or views/index.html
for every
template. Let’s have a look at the standard implementation:
def find_template(views, name, engine)
Tilt.mappings.each do |ext, klass|
next unless klass == engine
yield ::File.join(views, "#{name}.#{ext}")
end
end
As you can see, it will look in the views folder for a file named like the template with any of the file extensions registered for the template engine.
If all you want to change is the folder, you probably should just call super
:
def find_template(views, *a, &b)
super("#{views}/a", *a, &b)
super("#{views}/b", *a, &b)
end
More examples can be found in the readme.
Other changes
send_file
now takes a:last_modified
option- improved error handling