Mount Rack apps in Rails 3

by: Michael Raidel | posted: May 23rd, 2010

The routing in Rails 3 (powered by the rack-mount gem) offers a lot of improvements in functionality and syntax over the Rails 2 style. You can read up about them in the following posts:

One of the biggest improvements is the possibility to associate arbitrary rack apps with a specific route. There are different methods to configure the use of a Rack app in the router. One of them is the “match” method, which allows to map a path to a rack endpoint. The most common use of “match” is to specify a Rails controller and action (which can act like a rack app in Rails 3):

match "/blog" => "articles#index"

But instead of an action we also can route to any other rack app:

# using a proc as an example for the most simple rack app
match "/blog" => proc { |env| [ 200, {}, [ "my posts" ] ] }
# or a class representing a rack app
match "/blog" => MySinatraBlogApp
# with a glob to catch deeper nested routes
match "/blog(/*other_params)" => MySinatraBlogApp

You still have to do special work for the parts of the path following the “/blog”-prefix (for example specifying a glob so that the router knows to send requests looking like “/blog/archives” to the rack app). But the biggest disadvantage of this approach is that the Rack app has to know this path and use it in its own path recognition. In a request with the path “/blog/archives” the Sinatra app would look like this:

class MySinatraBlogApp < Sinatra::Base
  get "/blog/archives" do
    "my old posts"
  end
end

Anchors

Both problems can be solved by not “anchoring” the route. In the background a path given to “match” is converted to a regular expression by rack-mount. For “/blog” this expression would be /\A\/blog\Z/. The \A and \Z ensure that the path matches exactly and has no additional parts before or after the given route. If we don’t anchor the path the expression would be /\A\/blog/ allowing arbitrary paths after the “/blog”-prefix. Exactly what we want when mounting a rack app! Even better is that the PATH_INFO given to the rack app doesn’t include the prefix anymore which means that the app doesn’t have to know about the mount path.

match "/blog" => MySinatraBlogApp, :anchor => false
# now the sinatra app for /blog/archives can look like this
class MySinatraBlogApp < Sinatra::Base
  get "/archives" do
    "my old posts"
  end
end

Mount

There also is a routing method doing this configuration for us which is unsurprisingly named “mount”:

mount MySinatraBlogApp, :at => "/blog"
# or simply
mount MySinatraBlogApp => "/blog"

The definition is the other way round than when using match and the anchor option is set to false automatically (mount uses match behind the scenes).

Using the prefix in the rack app

How does the rack app know about the prefix (for example necessary to generate correct urls)? It can use the “SCRIPT_NAME” environment variable which is set to the prefix automatically. There is one problem left when using Sinatra as the rack-app: for the root-path (/blog) the PATH_INFO in the environment is set to “” instead of just “/”. This way Sinatra will not recognize the request if its action is defined with a “/”. Instead you would have to use an empty string at the moment!