Don't Fear the URLs
August 12, 2008 at 03:00 PM
I like models. They do the heavy lifting. They're easy to spec. You can grab them at the console or from script/runner with ease.
I like views. There isn't much to them - just an html document (or whatever) with some variables interspersed, and maybe a for loop here and there. Problems are easy to spot because views are so one-dimensional.
But controllers. Controllers, I've never liked. They offer a lot of infrastructure related to the request/response cycle which means they are clumsy to spec, and hard to access at the console. The prevailing view is to keep 'em skinny, and for good reason: they suck to work with, so spend as little time writing code in them as you can.
But what if mapping web URLs onto controller objects is a case of object-oriented mentality taken too far?
Maybe that's why I was looking to unify them with models. But here's a better idea: get rid of the objects, and while we're at it, unify controllers with routes.
Why is there separation between routes and controllers to begin with? Resource mapping is kinda cool, what with the helper urls. But building a url isn't very hard. A good url scheme makes it easy, in fact.
Consider a simple RESTful action in Rails:
map.connect '/user/:id/authkey', :controller => 'user', :action => 'show_authkey', :conditions => { :method => :get } class UserController < ApplicationController def show_authkey render :text => User.find(params[:id]).authkey end end
Seems like a lot of code to indicate that /user/:id/authkey should display the contents of that user's authkey code. What's worse, the routes are in a separate file from the controller, so knowing what parameters come out of the url (just :id in this case) takes some digging.
Sinatra unifies routes and controllers. Let's try that again:
get '/user/:id/authkey' do User.find(params[:id]).authkey end
Ahhhh...much better.
Why hide urls behind a bunch of abstraction? I like urls, at least when they're RESTful ones that are pretty to look at. They're easy to parse for both humans and computers. They clearly state the types of resources that your service provides, and the relationship between them. Obfuscating them through routes, url-building helpers, and ActiveResource doesn't provide much benefit that I can see.
Update: Blake Mizerany (author of Sinatra) pointed out that the Rails scaffold generator produces comments like this:
# GET /users/1 def show
Apparently even the Rails authors think that showing the URL is more understandable than the object/method-mapping.
Sinatra, My New Favorite Microframework
June 10, 2008 at 02:19 PM
A few months ago, I went in search of a way to build an extremely lightweight Ruby web app. Merb can be stripped down pretty far, but I wanted a true microframework. Ramaze and Camping were getting close, but didn't quite fit my taste. Then I discovered Sinatra.
Sinatra apps are typically written in a single file. It starts up and shuts down nearly instantaneously. It doesn't use much memory and it serves requests very quickly. But, it also offers nearly every major feature you expect from a full web framework: RESTful resources, templating (ERB, Haml/Sass, and Builder), mime types, file streaming, etags, development/production mode, exception rendering. It's fully testable with your choice of test or spec framework. It's multithreaded by default, though you can pass an option to wrap actions in a mutex. You can add in a database by requiring ActiveRecord or DataMapper. And it uses Rack, running on Mongrel by default.
One of the most important backend services for Heroku is written using Sinatra. We're now running several hundred instances of it in our cluster. It's performed like a champ - I haven't seen it die or leak memory, other than bugs in our app code.
Some interesting (though not necessarily meaningful) stats.
Lines of framework code (not counting tests or examples)
| Rails | 87,990 |
| Merb-core | 12,417 |
| Ramaze | 11,796 |
| Camping | 1,704 |
| Sinatra | 1,576 |
require 'sinatra' pulls in just one file: sinatra.rb. Now that's a commitment to small.
How about memory footprint? Camping takes the crown here, but Sinatra doesn't do too shabby:
Memory footprint of an empty application
| Rails | 52MB |
| Merb-core | 25MB |
| Ramaze | 18MB |
| Sinatra | 16MB |
| Camping | 7MB |
Aside: I got these numbers using the Linux free command before and after starting the server. Gauging real memory usage is very difficult because of shared pages, but free is much better than the VSZ/RSZ silliness you see in ps, which don't tell you very much.
But my real joy in Sinatra is its minimalist simplicity. The direct mapping of URLs to code (routes? who needs 'em?), the incredible ease of writing tests, and even just the simple fact that the return value from an action is its output. For example, an action might look like:
get '/posts/:id.xml' do Post.find(params[:id]).to_xml end
And a matching test:
should 'get a post in xml format' do Post.expects(:find).with('123') get_it '/posts/123.xml' end
When I return to writing Rails apps after working on Sinatra for a while, I sometimes find myself thinking: "wait, what did I need all this other crap for again?"