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)