Yehuda Katz is a member of the Ruby on Rails core team, and lead developer of the Merb project. He is a member of the jQuery Core Team, and a core contributor to DataMapper. He contributes to many open source projects, like Rubinius and Johnson, and works on some he created himself, like Thor.
How to Build Sinatra on Rails 3
August 26th, 2009
In Ruby, we have the great fortune to have one major framework (Rails) and a number of minor frameworks that drive innovation forward. One of the great minor frameworks which has been getting a lot of traction recently is Sinatra, primarily because it exposes a great DSL for writing small, single-purpose apps.
Here’s an example of a simple Sinatra application.
class MyApp < Sinatra::Base set :views, File.dirname(__FILE__) enable :sessions before do halt if session[:fail] == true end get "/hello" do "Hello world" end get "/world" do @name = "Carl" erb :awesomesauce end get "/fail" do session[:fail] = true "You failed" end end
There’s a lot of functionality packed into this little package. You can declare some code to be run before all actions, declare actions and the URL they should be routed from, use rendering semantics, and even use sessions.
We’ve been saying that Rails 3 is flexible enough to use as a framework toolkit–let’s prove it by using Rails to build the subset of the Sinatra DSL described above.
Let’s start with a very tiny subset of the DSL:
class MyApp < Sinatra::Base get "/hello" do "HELLO World" end post "/world" do "Hello WORLD" end end
The first step is to declare the Sinatra base class:
module Sinatra class Base < ActionController::Metal include ActionController::RackConvenience end end
We start off by making Sinatra::Base a subclass of the bare metal ActionController implementation, which provides just enough infrastructure to get going. We also include the RackConvenience module, which provides request and response and handles some basic Rack tasks for us.
Next, let’s add support for the GET and POST method:
class Sinatra::Base def self.inherited(klass) klass.class_eval { @_routes = [] } end class << self def get(uri, options = {}, &block) route(:get, uri, options, &block) end def post(uri, options = {}, &block) route(:post, uri, options, &block) end def route(http_method, uri, options, &block) action_name = "[#{http_method}] #{uri}" @_routes << {:method => http_method.to_s.upcase, :uri => uri, :action => action_name, :options => options} define_method(action_name, &block) end end end
We’ve simply defined some class methods on the Sinatra::Base to store off routing details for the get and post methods, and creating a new method named [GET] /hello. This is a bit of an interesting Ruby trick; while the def keyword has strict semantics for method names, define_method allows any string.
Now we need to wire up the actual routing. There are a number of options, including the Rails router (rack-mount, rack-router, and usher are all new, working Rails-like routers). We’ll use Usher, a fast Rails-like router written by Josh Hull.
class << Sinatra::Base def to_app routes, controller = @_routes, self Usher::Interface.for(:rack) do routes.each do |route| add(route[:uri], :conditions => {:method => route[:method]}.merge(route[:options])). to(controller.action(route[:action])) end end end end
Here, we define to_app, which is used by Rack to convert a parameter to run into a valid Rack application. We create a new Usher interface, and add a route for each route created by Sinatra. Because Usher::Interface.for uses instance_eval for its DSL, we store off the routes and controller in local variables that will still be available in the closure.
One little detail here: In Rails 3, each action in a controller is a valid rack endpoint. You get the endpoint by doing ControllerName.action(method_name). Here, we’re simply pulling out the action named “[GET] /hello” that we created in route.
The final piece of the puzzle is covering the action processing in the controller itself. For this, we will mostly reuse the default action processing, with a small change:
class Sinatra::Base def process_action(*) self.response_body = super end end
What’s happening here is that Rails does not treat the return value of the action as significant, instead expecting it to be set using render, but Sinatra treats the returned string as significant. As a result, we set the response_body to the return value of the action.
Next, let’s add session support.
class << Sinatra::Base def set(name, value) send("_set_#{name}", value) end def enable(name) set(name, true) end def _set_sessions(value) @_sessions = value include ActionController::Session if value end def to_app routes, controller = @_routes, self app = Usher::Interface.for(:rack) do routes.each do |route| add(route[:uri], :conditions => {:method => route[:method]}.merge(route[:options])). to(controller.action(route[:action])) end end if @_sessions app = ActionDispatch::Session::CookieStore.new(app, {:key => "_secret_key", :secret => Digest::SHA2.hexdigest(Time.now.to_s + rand(100).to_s)}) end app end end
There’s a few things going on here. First, Sinatra provides an API for setting options: set :option, :value. In Sinatra, enable :option is equivalent to set :option, true. To simplify adding new options, we just delegate set :whatever, value to a call to _set_whatever(value).
We then implement _set_sessions(value) to include ActionController::Session, which provides the session helper. In to_app, we wrap the original application in an ActionDispatch::Session::CookieStore if sessions were set.
Next, we want to add in support for callbacks (before do). It’s only a few lines:
class Sinatra::Base include AbstractController::Callbacks end class << Sinatra::Base alias before before_filter end
Basically, we pull in the normal Rails callback code, and then rename before_filter to before and we’re good to go.
Finally, let’s dig into rendering.
class Sinatra::Base include ActionController::RenderingController def sinatra_render_file(name) render :template => name.to_s end def sinatra_render_inline(string, type) render :inline => string, :type => type end %w(haml erb builder).each do |type| define_method(type) do |thing| return sinatra_render_inline(thing, type) if thing.is_a?(String) return sinatra_render_file(thing) end end end class << Sinatra::Base alias _set_views append_view_path end
We include the RenderController module, which provides rendering support. Sinatra supports a few different syntaxes for rendering. It supports erb :template_name which renders the ERB template named template_name. It also supports erb "Some String", which renders the string uses the ERB engine.
Rails supports both of those via render :template and render :inline, so we simply defer to that functionality in each case. We also handle Sinatra’s set :views, view_path by delegating to append_view_path.
You can check out the full repository at https://github.com/wycats/railsnatra/
So there you have it, a large subset of the Sinatra DSL written in Rails in under 100 lines of code. And if you want to add in more advanced Rails features, like layouts, flash, respond_to, file streaming, or conditional get support, it’s just a simple module inclusion away.

AJ (fujin), Posted August 27, 2009, 2:00 pm
The ‘s inside of your code blocks are rendered as normal HTML :(
wycats, Posted August 27, 2009, 2:17 pm
@AJ fixed
Geoffrey Grosenbach, Posted August 27, 2009, 6:49 pm
Can you do this again, but make it run PHP code?
Jinzhu, Posted August 27, 2009, 8:15 pm
very cool.
Andre Lewis, Posted August 28, 2009, 12:13 am
Very cool, Yehuda!
Devin, Posted August 28, 2009, 1:42 am
Thanks Yehuda — Very cool.
Sebastian, Posted August 28, 2009, 2:27 am
Cool, indeed. Have you any numbers how it compares to the original sinatra-code?
Lourens Naudé, Posted August 28, 2009, 4:49 am
Interesting post, I estimate 2 diet cokes amongst carlhuda.
raggi, Posted August 28, 2009, 4:53 am
In the voice of Dave Chappelle as Rick James: “I’m sorry Frank Sinatra, I was having too much fun!”
raggi, Posted August 28, 2009, 4:54 am
“Dr. Peppers one hell of a drug”
Satish Talim, Posted August 28, 2009, 6:25 am
Yehuda rocks!!
Scudis, Posted August 28, 2009, 7:21 am
If I understand correctly you just need to rewrite sinatra and you can use it inside Rails.
Guoliang Cao, Posted August 28, 2009, 8:30 am
Very cool! Thanks.
cbmeeks, Posted August 28, 2009, 12:14 pm
Yehuda, that is awesome. But a little over my head right now. lol
@Geoffrey first, I thought you were kidding but I could actually see a use for this. Drupal on Rails? :-)
dreamcat4, Posted August 28, 2009, 2:25 pm
Now sure why you’d benefit more from this method. It seems like a lot of re-writing of the Sinatra code. You might otherwise specify sinatra as an extra rack middleware layer in your config.ru file. Still have access to some rails environment, for example the activerecord models. An evolution of Pratik’s method. See http://bit.ly/19bYmP, http://bit.ly/5T51g
wycats, Posted August 28, 2009, 3:00 pm
@dreamcat4 two things
1) It’s just a demo to show the flexibility of the new Rails architecture; you’re probably better off using Sinatra for Sinatra.
2) You can pull in any of the advanced functionality of Rails for free into a Railsnatra app. For instance, to use conditional GET, simply pull in the ConditionalGet module and fresh_when at al will simply work. The implementation in this blog post is just a demo, but if someone wanted to take up the banner they could make it work quite well.
Ho-Sheng Hsiao, Posted August 28, 2009, 5:03 pm
Ever since I saw the slides from Katz’s presentation in Japan, my head has been spinning with the possibilities. They are based on the following assumptions:
(1) Each of the module can be swapped out because they comply with APIs … taking Ruby ducktyping to the next level.
(2) The concept of ActiveMailer and ActiveController having shared code in AbstractController means the possibility of using non-HTTP dispatches and routing.
So talking with @rahsunmcafee (twitter), we came up with some of the implications:
* A real WordPress-like engine instead of faking it with Rails 2. One that lets you set up a blog very quickly, yet when it comes time to add embedded apps, you have the entirety of the Rails 3 stack to work with (the best parts of WordPress minus its worst parts).
* A real Django/Drupal/etc.-like CMS engine instead of faking it with Rails 2.
* A CRM that doesn’t have to struggle (like XLSuite).
* A workflow engine that can talk Rack Endpoint instead of rolling your own ad-hoc bridge.
* Pure email applications that can be driven through a SQL (or NoSQL) backend and erb/haml/whatever templating.
* HTTP-Bosh dispatcher. Or better yet, an XMPP listener/dispatcher: a framework for building Google Wave apps (while taking advantage of AR/Datamapper/Sequel/et. al)
* AMQP dispatcher? Might as well talk about asynchronous, stateful dispatches. How about being able to play well with the weird merchant gateway protocols?
From where I’m standing, it seems to me the major thing about Rails 3 is the ability to change the convention itself to fit the app you are writing. It’s not so much that you can reconfigure Rails 3 so much as rebuilding Rails 2 using Rails 3 components — just like creating that Sinatra DSL using Rails 3 components. It means you can create a DSL specific to an app yet have it play nice without fighting against Rails 2 conventions. And if you just want to work with Rails 2 style apps, you still can.
Tj, Posted August 30, 2009, 4:39 pm
Sinatra is better than Rails IMO with a few abstracted libraries it is nearly as powerful without most the cruft
Tomás Pollak, Posted September 15, 2009, 6:01 am
Nice one Yehuda. I can wait to try out Rails3 and see how “merbizised” it has become (i.e. leaner & meaner :).
Arjun Ghosh, Posted November 9, 2009, 6:01 pm
Yehuda, very nice and useful post on people getting introduced to Sinatra
jd, Posted February 1, 2010, 12:07 am
A very nice proof of concept. Actually it’s funny to see how Rails3 became kind of the Light Framework by (simply?) having it more object-oriented/modular. Thanks for this series of features and use-cases reviews, Yehuda!
Jônatas Paganini, Posted February 16, 2010, 8:06 am
I’ve been using Sinatra pure, but when I need to get a confortable place to code, I require the actionpack, actionview and a bunch of cool gems. This wasn’t my best choice, I think that the best is combine Rails 3 whith a Rack’s powerful.