Note: The API has significantly changed since this posting. Best to check out this post for details.

With Ezra’s approval, I committed some significant changes to the Merb routing system last night. If you’ve ever been limited by Rails’ routing system, you might want to check out some of the features now in Merb. Here’s a run-down of some ways you can use the new system:

Construct Params from Matched Portions of the URL

Merb::Router.prepare do |r|
  # There is now a named_route method which is
  # basically a simple_route plus a URL generator
  r.named_route :test,
    "/:controller/:action/:id/:prefix",
    :action => ':prefix_:action'
end

# In a controller or view, we can use its generator:
url :test,
  :controller => "users",
  :action => "show",
  :id => 1,
  :prefix => "alternate"

# => "/users/show/1/alternate

# Example controller class that would respond properly:
class Users < Application
  def simple_show
    #
  end
  def alternate_show
    #
  end
end

Use Any Request / Environment Variable

Merb::Router.prepare do |r|
  # Send all SSL / HTTPS traffic to the Secure class, 'index' action
  r.route :protocol => /https/,
    :controller => "secure",
    :action => "index"

  # Use the 'admin' subdomain to route to the admin module
  r.route :domain => /^admin\\./,
    :path => %r[/~~/~~/~~],
    :controller => “admin/:path[1]“,
    :action => “:path[2]“,
    :id => “:path[3]”
end

Note in the above example that some of the keys in the hash are environment variables (:domain, :path) and some are outputs to the params hash (:controller, :action, :id). This may change in the future, but for now it just means you have to know that the environment variables are “reserved words”, used specifically for matching against the incoming URL request.

Also, you may wonder at the “~~” and “:path[1]” notation. The “~~” in the path is a special string sequence that gets replaced with a parenthetical regular expression that will match a normal URL segment (e.g. it will match characters such as a-z but not ‘/’ or ‘?’). Next, the “:path[1]” notation is used to tell the router how to reconstruct the params from the matched portions of the request / environment variables. So, in the above example, the :controller is going to be “admin/” plus whatever gets matched in the first “~~”. Here’s an example match. Let’s use the URL, “http://admin.mysite.com/books/show/1″:

# Routes to the controller Admin::Books, and the "show" action
module Admin
  class Books < Application
    def show
      params[:controller] # => “admin/books”
      params[:action] # => “show”
      params[:id] # => “1″
    end
  end
end

A full list of request / environment variables that you can use is available in the lib/merb/router.rb class, but here is a sampling:

  • protocol
  • domain
  • port
  • path
  • method (e.g. ‘put’, ‘post’, ‘get’)
  • query_string
  • accept
  • remote_ip
  • user_agent
  • referer

This flexibility could, for example, let you essentially create two different sets of views for Mozilla vs. Internet Explorer browsers (routes would distinguish between browsers using the “user_agent” variable). Or you could ban certain IP addresses. Or allow certain routes from third party sites using the “referer”. Lots of possibilities.

Late-bound Route Decisions

Merb::Router.prepare do |r|
  # Query the database for an object type, and then route
  # to the appropriate controller based on type
  r.route do |request|
    if request.path.match(%r{/([a-z0-9\-] )})
      guid = $1
      if obj = AppObject.find_by_guid(guid)
        {:controller => obj.type, :action => ’show’, :id => guid}
      end
    end
  end
end

Some things may continue to change as I get feedback and also optimize the system, but the above examples should stand as a doorway for solutions to your own development needs. Let me know if you have any special requests and I’ll try to post more later.