Sinatra and the class/instance distinction

Wed, 04 May 2011 13:39:05 +0000

The Sinatra microframework is described as enabling “Classy Web Development”, and it turns out this is more literally true than I previously thought.

The Rack Specification says

A Rack application is an Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.

(emphasis mine). When you write a Sinatra app, though, it seems to want a class: whether you call MyApp.run! directly (we assume throughout this post that MyApp is a Sinatra::Base subclass) or use a config.ru or any other way to start the app running, there is a conspicuous lack of MyApp.new anywhere around. Yet the Rack spec says an app is an instance.

At first I thought I was being silly or didn’t understand how Rack works or had in general just misunderstood something, but it turns out not. Some ferretting through Sinatra source code is needed to see how it does this, but the bottom line is that MyApp has a class method MyApp.call which rack invokes, and this delegates to (after first, if necessary, instantiating) a singleton instance of MyApp stored in the prototype field. I am not at all sure why they did this. It may just be a hangover from Sinatra’s heritage and this stuff came along for the ride when Sinatra::Base was factored out of the Sinatra::Application classic app support. Or they may have a perfectly good reason (this is the hypothesis I am leaning towards and I suspect that “Rack middleware pipelines” is that reason). For my purposes currently it’s probably sufficient to know that they do it without needing to know why, and that I should stop trying to write Sinatra::Base subclasses which takes extra parameters to new.

:; irb -r sinatra/base
ruby-1.9.2-p0 > class MyApp < Sinatra::Base ; end
 => nil 
ruby-1.9.2-p0 > MyApp.respond_to?(:call)
 => true 
ruby-1.9.2-p0 > begin; MyApp.call({}); rescue Exception => e ;nil;end
 => nil 
ruby-1.9.2-p0 > MyApp.prototype.class
 => Sinatra::ShowExceptions 

Ta, and with emphasis, da! (The begin/end around MyApp.call is because for the purpose of this example I am too lazy to craft a legitimate rack environment argument and just want to demonstrate that prototype is created. And we should not be surprised that the prototype’s class is not the same class as we created, because there is middleware chained to it. In summary, this example may be more confusing in its incidentals than it is illuminating in its essentials. Oh well)