Some recent news in the Ruby web development community, for those who haven’t been following it, or have been asleep of late:

Rails and Merb are merging.

Rails 2.3 has a shiny new engine, and lets you program to the metal.

Micro frameworks like Sinatra are getting a lot of interest for smaller applications

Everyone seems to be talking about Rack.

The common thread here is modularity. It’s getting easier and easier to build up a complex web application using smaller, more easily understood pieces.

One of the expected outcomes of the Rails Merb merger is getting a well described (in terms of tests) API for plugins, and the ability to choose different modules like ORMs (ActiveRecord or DataMapper or something else anyone.)

The new spin on “engines” in Rails 2.3 makes it easy to develop reusable Rails sub-applications with their own MVC struction which can be plugged into an other Rails application

Rails metal allows you to define high-performance code which can respond to certain http requests before they even get to the Rails action pack routing and dispatching mechanism. This is something which should be reserved for when it’s needed for things like frequently executed AJAX requests, but it’s nice that it’s there.

I didn’t mention it in the list above, but Rails also lets you introduce other apps in the form of ‘middleware’, a term which I’ll explain in a paragraph or two. By following some middleware patterns, you can incorporate non-rails apps (Sinatra anyone?) with a Rails application (including plugged in engines) as part of a larger whole.

With, I think, the exception of engines, all of this depends on Rack, which implements a very simple and powerful idea. Rack provides a simple one method API by which a web server, either directly or through an agent called a rack handler, talks to an application. The application just needs to respond to a call message with a single bog standard Ruby Hash as the sole argument, called env by convention, and return a three element array containing an http response code, an object representing the response headers (normally just a Hash), and an object representing the response body which must respond to each, yielding each line of the response. In Ruby prior to 1.9 the response body can be a String with lines separated by new line characters. This won’t work in Ruby 1.9 though since String#each is no longer there, of course you could implement it, or alias it to String#lines.

The really cool thing about Rack is that the API allows applications to be composed. This is where the term ‘middleware’ comes in. Think of Unix pipes where the output of one process can become the input to another, with a “DSL” in the shell for configuring this arrangement. A rack application could do things like forward the request to another application and modify and or log the result, or modify and or log a request before forwarding, or decide to process certain urls itself instead of another application, or have a list of applications and return the results from the first one which returns something other that a 404 “Not Found” status, or decide which application to forward the request to based on matching rules on the url, host and port. Rack provides apps which do many of these things. A Rails metal is implemented as a Rack app which either processes a request or returns a 404 status, in which case rails dispatches the request.

The Rack equivalent to the Unix shell DSL of pipes and redirects is Rack::Builder, which provides a very simple dsl for creating a rack app which chains multiple rack apps. Buildng an app with Builder is quite simple, you call RackBuilder.app with a block. This makes a builder, and instance_eval’s the block with the builder, and then returns sends to_app to the builder which returns a rack app which does the right thing. Inside the block you can do three things:

Add a Rack app to the end of the chain: run app

Instantiate a Rack app passing a class, any instantiation arguments, and optionally a block to be given to the classes initialize method, and add it to the end of the chain: use Rack :: CommonLogger , STDERR

Add an application to a map at the end of the chain which will be called if the url or path in the request matches a prefix. The application is defined by a builder block. e.g. map('/foo') do # builder dsl to create app to process url paths starting with '/foo' end map('/bar') do # builder dsl to create app to process url paths starting with /bar' end

Rack in Rails 2.3

The following is based on Rails 2.3 RC 1.

Middleware configuration

Rails supports something inspired by Rack::Builder for inserting middleware before the Rails dispatcher. The following is based on Rails 2.3 RC 1. In the initializer block in environment.rb you can add calls like:

config.middleware.use "MyApp", "some initialization argument" config.middleware.use(Rack::Builder) do # rack builder dsl here end

The Rails middleware configuration “DSL” isn’t quite the same as Rack::Builder. First, only use is supported, not run or map. You can pass a string instead of a class name to use, it will be constantized to obtain the class. You can also pass something which responds to call (e.g. a lambda) which will be used to obtain the class for the App. You can also add an option argument :if with a lambda for its value, which can be used to control whether or not a particular middleware app should be used based on, say another environment variable when the Rails app is initialized.

Integrated Session Management

Rails inserts a rack app near the beginning of the middleware stack which adds the rails session key and session options to the environment, with keys ‘rack.session’, and ‘rack.session.options’ respectively. This allows non-rails rack apps in the stack to have access to the rails session.

It’s not clear to me whether or not this is standard usage, since the Rack specification seems to imply that env keys starting with ‘rack.’ are reserved and shouldn’t be extended.

Summary

It’s exciting to see this amount of common superstructure being adopted by the Ruby web development framework providers, and I’ll be fascinated to see what comes of it all.