Rails Routes: match and mount

While working on the SlugEngine built into this blog site, I came across a note-worthy errata in the book Rails 3 in Action.

Section 18.3.1 states that the following two lines in config/routes.rb are functionally equivalent:

# method one
mount Foo::Bar, :at => 'foo/bar'

# method two
match 'foo/bar', :to => Foo::Bar

These two routes are similar. In fact, mount in turn calls match to complete the route setup. However, the difference (as of Rails 3.1.1) lies in the options that mount passes along to match.

Take a look at the mount method, as defined in action_dispatch/routing/mapper.rb:

def mount(app, options = nil)
  if options
    path = options.delete(:at)
  else
    options = app
    app, path = options.find { |k, v| k.respond_to?(:call) }
    options.delete(app) if app
  end

  raise "A rack application must be specified" unless path

  options[:as] ||= app_name(app)

  match(path, options.merge(:to => app, :anchor => false, :format => false))

  define_generate_prefix(app, options[:as])
  self
end

Near the end of the method body, it calls match. At this point, whatever options have been established to this point are merged with some defaults, which will overwrite whatever options were declared in config/routes.rb.

Specifically, the mount method overrides and sets the :anchor option to false.

The net effect of setting :anchor => false is that the request path is that the mount point is stripped from the request path and inserted in the script name request parameter. This magic happens in Rack::Mount::Prefix as follows:

def call(env)
  if prefix = env[KEY] || @prefix
    old_path_info = env[PATH_INFO].dup
    old_script_name = env[SCRIPT_NAME].dup

    begin
      env[PATH_INFO] = env[PATH_INFO].sub(prefix, EMPTY_STRING)
      env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH
      env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix)
      @app.call(env)
    ensure
      env[PATH_INFO] = old_path_info
      env[SCRIPT_NAME] = old_script_name
    end
  else
    @app.call(env)
  end
end

Most of the time, this probably isn’t a big deal, but it can have an impact on route helpers if used by the mounted application. The route helpers will pick up on the env[SCRIPT_NAME] and assume that any routes they generate should be prefixed by this value. So even root_path would become /foo/bar instead of / in the example above.

The easy solution to this is to use match instead of mount in config/routes.rb.

match 'foo/bar', :to => Foo::Bar

Using this route, root_path returns /, as expected.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>