Routing

When Scorched receives a request, the first thing it does is iterate over it's internal mapping hash, looking for the any URL pattern that matches the current URL. If it finds an appropriate match, it invokes the call method on the target defined for that mapping, unless the target is a Proc, in which case it's invoked via instance_exec to run it within the context of the controller instance.

Mappings can be defined manually using the map class method, also aliased as <<. Besides the required URL pattern and target elements, a mapping can also define a priority, and one or more conditions. The example below demonstrates the use of all of them.

map pattern: '/', priority: -99, conditions: {method: ['POST', 'PUT', 'DELETE']}, target: proc { |env|
  [200, {}, 'Bugger off']
}

The position the new mapping is inserted into the mapping hash is determined by it's priority, and the priority of the mappings already defined. This avoids re-sorting the mapping hash every time it's added to. This isn't a performance consideration, but is required to maintain the natural insert order of the mappings which have identical priorities (such as the default 0).

A mappings method is also provided as means to access all defined mappings on a controller, but it should be considered read-only for the reasons just stated.

Route Helpers

Adding mappings manually can be a little verbose and painful, which is why Scorched includes a bunch of route helpers which are used in most code examples.

The main route helper which all others delegate to, is the route class method. Here's what it looks like in both it's simple and advance forms:

route '/' do
  'Well hello there'
end

route '/*', 5, method: ['POST', 'PUT', 'DELETE'] do |capture|
  "Hmm, I see you're trying to change the resource #{capture}"
end

You can see pretty clearly how these examples correspond to the pattern, priority, conditions and target options of a manual mapping. The pattern, priority and conditions behave exactly as they do for a manual mapping, with a couple of exceptions.

The first exception is that the pattern must match to the end of the request path. This is mentioned in the pattern matching section below. You can also define a route with multiple patterns by using an array. This creates a different mapping for each URL, but using the same proc object and other arguments.

The other more notable exception is in how the given block is treated. The block given to the route helper is wrapped in another proc. The wrapping proc does a couple of things. It first sends all the captures in the url pattern as arguments to the given block; this is shown in the example above. The other thing it does is takes care of assigning the return value to the body of the response.

In the latter of the two examples above, a :method condition defines what methods the route is intended to process. The first example has no such condition, so it accepts all HTTP methods. Typically however, a route will handle a single HTTP method, which is why Scorched also provides the convenience helpers: get, post, put, delete, head, options, patch, link and unlink. These methods automatically define the corresponding :method condition, with the get helper also including head as an accepted HTTP method.

Pattern Matching

All patterns attempt to match the remaining unmatched portion of the request path; the request path being Rack's path_info request variable. The unmatched path will always begin with a forward slash if the previously matched portion of the path ended immediately before, or included as the last character, a forward slash. As an example, if the request was to "/article/21", then both "/article/" => "/21" and "/article" => "/21" would match.

The path_info used to match against is unescaped, meaning percent-codes are resolved, e.g. %20 resolves to a space. The two exceptions are the escaped forward-slash and percent sign, which remain escaped as %2F and %25 respectively.

The forward-slash cannot be automatically escaped as it would make it impossible to disambiguate from an actual forward-slash in the URL (which has special meaning). The encoded percent-sign thus also needs to remain unescaped, otherwise it'd be impossible to safely unescape the escaped forward-slash in your application, if you needed to. If this all sounds very confusing, rest assured you probably won't ever encounter a scenario in which you'd have to think about this.

All patterns must match from the beginning of the path. So even though the pattern "article" would match "/article/21", it wouldn't count as a match because the match didn't start at a non-zero offset.

If a pattern contains named captures, unnamed captures will be lost - this is how named regex captures work in Ruby. So if you name one capture, make sure you name any other captures you may want to access.

Patterns can be defined as either a String or Regexp.

String Patterns

String patterns are compiled into Regexp patterns corresponding to the following rules:

Regex Patterns

Regex patterns offer more power and flexibility than string patterns (naturally). The rules for Regex patterns are identical to String patterns, e.g. they must match from the beginning of the path, etc.

Symbol Matchers

Symbol matchers were added in v0.25 as a way to conveniently name and re-use regular expressions for matching. Additionally, symbol matchers allow one to define a Proc to pre-process the matched string. This can be used to coerce a value into a particular type (such as an integer), or to manipulate the string in some other way.

Two symbol matchers are included. :numeric and :alpha_numeric. These are more for example sake than anything else, as it's intended users will implement symbol matchers specific to their application. For example:

symbol_matchers[:article_id] = /[a-z0-9\-]+-[0-9]{1,6}/

The above symbol matcher would match an article id in a typical blog friendly-URL, e.g. hello-world-453. We can further improve this symbol matcher by using a Proc to remove everything but the numeric id at the end, converting it to an integer at the same time.

symbol_matchers[:article_id] =[/[a-z0-9\-]+-([0-9]{1,6})/, proc { |v| v.split('-').last.to_i }]

One limitation to be aware of is that like named captures, if you use the same symbol matcher more than once in a single pattern, you only be able to access the first capture.

get '/:article_id/:article_id' do
  captures[:article_id] #=> Will always equal the value of whatever first :article_id captured.
end

Captures

Captures can be accessed as arguments on the route proc, or via the captures helper method, which is shorthand for request.captures.

get '/:id' do |id|
  id == captures[:id]
end

get '/*/*' do |id, title|
  [id, title] == captures
end

The above examples demonstrates the two methods of accessing captures, for both named and anonymous captures. You may notice that while named and anonymous captures are passed to the proc as arguments in the exact same way, the captures helper either returns either a Hash or an Array depending on whether the captures are named or not.

Conditions

Conditions are essentially just pre-requisites that must be met before a mapping is invoked to handle the current request. They're implemented as Proc objects which take a single argument, and return true if the condition is satisfied, or false otherwise. Scorched comes with a number of pre-defined conditions included, some of which use functionality provided by rack-accept - one of the few dependancies of Scorched.

As of v0.11, Scorched also supports inverted/negated conditions by adding a trailing exclamation mark. For example, a route with the condition method!: 'GET' will match any HTTP request except for GET requests.

Like configuration options, conditions are implemented using the Scorched::Options class, so they're inherited and can be overridden by child classes. You may easily add your own conditions as the example below demonstrates.

conditions[:has_permission] = proc { |v|
  user.has_permission == v
}

get '/', has_permission: true do
  'Welcome'
end

get '/', has_permission: false do
  'Forbidden'
end

Each of the built-in conditions can take a single value, or an array of values, with the exception of the :host and :user_agent conditions which support Regexp patterns.