SqueezeBox
Yet Another Web Framework
SqueezeBox extends the Mongrel web server to provide an effortless web framework.
Designed after the Model-View-Controller pattern, SqueezeBox separates presentation and back-end cleanly but slightly differently than contemporary systems. The major difference between Rails, Merb, or the other Ruby frameworks is that SqueezeBox routes HTTP requests based on file system organization. (Like mod_php!) While some may scoff at this simplistic design, filesystem routing significantly lessens the complexity of usage.
Despite being little more than a directory of .erb files, SqueezeBox sites still have models and controllers. The model part is typical: ActiveRecord. The controller part is novel.
A SqueezeBox website is divided into Resources (where Resource does not have the same meaning as it does in Rails). A Resource is an extension-less path in the document root — this path can match one or more files. For example the path /products might match several files
-
/home/web_store/public/products.erb, an HTML template -
/home/web_store/public/products.rb, aResourcecontroller file
products.erb or something else entirely could be returned from a HTTP request to /products, depending on what the controller file, products.rb instructs.
The controller file is similar to an action in Rails.
# /home/web_store/public/products.rb @products = Product.find(:all) renderThe erubis templates are the same:
<!-- /home/web_store/public/products.erb --> <h1>Here are our Products!</h1> <ul> <% for product in @products %> <li><%= product.name %></li> <% end %> </ul>
As with mod_php, Resources with directory names can be targeted by using index.rb and index.erb.
While SqueezeBox routes by the file-system like antiquated Apache CGI modules (like PHP), there are many old-fashioned annoyances that it does not copy. For example, one does not need to include model definitions or header/footer files into each erb file. Rather when SqueezeBox starts up, it looks in a special file called initialize.rb for instructions on what libraries to load globalally, what layouts should be availible, or other instructions.
Getting Started
There is nothing to using SqueezeBox. Here is everything you need to know. To install it:
~> sudo gem install squeezebox
To start new website:
~> mkdir -p new_website/public ~> echo "Now is <%= Time.now %>" > new_website/public/index.erb
Then start up the server by running the squeezebox command with the website directory as an argument.
~> squeezebox -p 4200 new_website/
http://localhost:4200 should show the date and time.
The Controller File
Note that in Ruby the last line of execution is the return value. Explicit use of return is allowed but not often used. When the last line of a controller file is render, the return value of the controller file is the output of the function render (a string containing a rendered erb template).
Controller files are functions whose return value is the body of the HTTP response. Typically one will call render() to render the erb template (contained in the same Resource path) but there are numerous other possibilities. Returning just a string use that string as the body of the response—useful for sending back JSON data to a client. Returning any IO object (or anything that acts like an IO object) will prompt SqueezeBox to stream, in chunks, data to the response body. If you want to stream dynamic content (perhaps JSON containing updates) you need only define a class with a read method and return an instance of that from a Controller file.
The controller file has access to all the relevant data:
-
@requestis the genuineMongrel::HttpRequestobject if you require low-level access. -
@paramsis a hash containing anyPOSTorGETparameters. -
@cookiesis a hash of cookies associated with the domain. -
@headersis a hash of HTTP headers to be returned to the client.
Any instance variables assigned in the controller file (i.e., ones with @ signs) will be available to erb templates.
The function redirect_to() sets the status code to 301 for redirection and returns nil (which is what the body of the response should be for redirection).
Layouts
Layouts are an important part of web design—one would like their site to look consistent. In SqueezeBox one can define a default layout which is used automatically throughout the site unless otherwise specified. Layouts must be defined before SqueezeBox starts, in the initialize.rb file. The function to define layouts is set_layouts
set_layouts( :default => 'layouts/default.erb', :blue => 'layouts/blue.erb', :admin => 'layouts/admin.erb' )
This commands initializes three layouts each identified but a symbol. When render() is called from a controller file an option :layout => :blue would tell SqueezeBox to render with layouts/blue.erb. Automatically render() will use the :default layout; to avoid this use render(:layout => false).
Layout templates should be placed outside of the public/ directory so SqueezeBox does not accidentally serve them.
A layout template has a special variable, content which will be replaced with the content of the inner template. Example:
<html>
<head>
<title>
<%= @page_title ? @page_title : 'My Site' %>
</title>
<link type="text/css" rel="stylesheet" href="/site.css"/>
</head>
<body>
<h1>
<%= @page_title ? @page_title : 'My Site' %>
</h1>
<div id="mainContent"><%= content %></div>
<div id="footer">:D</div>
</body>
</html>
Note that @page_title can be defined inside the inner template yet effect the layout template.
Exceptions
SqueezeBox uses Ruby-level exceptions to display HTTP errors. It has a number of predefined exceptions which can be used. For example using
raise NotFoundfrom a controller file will stop current execution and display a page with status code 404 telling the user that the document could not be found. Similarly
raise Unauthorizedshows a page with a 501 error telling the user they do not have access rights to the requested information. A common exception would be denying requests unless they are using the
POST method:
raise MethodNotAllowed unless @request.post?
These exceptions are actually genuine Resources with paths at /exceptions/not_found and /exceptions/unauthorized. That means that if you place not_found.erb in public/exceptions, SqueezeBox will use your template, and your controller to display the 404 page.
Exceptions that are raised during execution (syntax errors, out of memory, etc) which are not special SqueezeBox exceptions look at /exceptions/index for instructions and return with 500 status code (internal server error).
This is really powerful! Now you have a way to display dynamic error pages, perhaps logging Unauthorized requests to a database, or rendering a 404 page that knows what the request was.
Design Philosophy
- No magic Ruby: The code should be expressive and fast
- No generators: Make the API simple enough to start immediately from nothing. A large generated file-tree is as convoluted as a complex API.
-
Keep it fast: Rely on web servers as much as possible (e.g. send files with the
X-Sendfileheader), SqueezeBox is only meant to be run behind a full featured server. Only features that every single website will need should be included in the core, everything else should be pushed into add-on libraries that can be loaded easily frominitialize.rb.
More
Please look at the documentation and at the example web site included in the distribution for more information.
The mailing list is squeezebox-users@rubyforge.org. The FreeNode IRC channel is #squeezebox. The bug tracker is on RubyForge and the SVN repository is at
http://tinyclouds.org/svn/squeeze_box/trunk/