module Roda::RodaPlugins::HashRoutes

  1. lib/roda/plugins/hash_routes.rb

The hash_routes plugin combines the O(1) dispatching speed of the static_routing plugin with the flexibility of the multi_route plugin. For any point in the routing tree, it allows you dispatch to multiple routes where the next segment or the remaining path is a static string.

For a basic replacement of the multi_route plugin, you can replace class level route('segment') calls with hash_branch('segment'):

class App < Roda
  plugin :hash_routes

  hash_branch("a") do |r|
    # /a branch
  end

  hash_branch("b") do |r|
    # /b branch
  end

  route do |r|
    r.hash_branches
  end
end

With the above routing tree, the r.hash_branches call in the main routing tree, will dispatch requests for the /a and /b branches of the tree to the appropriate routing blocks.

In addition to supporting routing via the next segment, you can also support similar routing for entire remaining path using the hash_path class method:

class App < Roda
  plugin :hash_routes

  hash_path("/a") do |r|
    # /a path
  end

  hash_path("/a/b") do |r|
    # /a/b path
  end

  route do |r|
    r.hash_paths
  end
end

With the above routing tree, the r.hash_paths call will dispatch requests for the /a and /a/b request paths.

You can combine the two approaches, and use r.hash_routes to first try routing the full path, and then try routing the next segment:

class App < Roda
  plugin :hash_routes

  hash_branch("a") do |r|
    # /a branch
  end

  hash_branch("b") do |r|
    # /b branch
  end

  hash_path("/a") do |r|
    # /a path
  end

  hash_path("/a/b") do |r|
    # /a/b path
  end

  route do |r|
    r.hash_routes
  end
end

With the above routing tree, requests for /a and /a/b will be routed to the appropriate hash_path block. Other requests for the /a branch, and all requests for the /b branch will be routed to the appropriate hash_branch block.

Both hash_branch and hash_path support namespaces, which allows them to be used at any level of the routing tree. Here is an example that uses namespaces for sub-branches:

class App < Roda
  plugin :hash_routes

  # Only one argument used, so the namespace defaults to '', and the argument
  # specifies the route name
  hash_branch("a") do |r|
    # uses '/a' as the namespace when looking up routes,
    # as that part of the path has been routed now
    r.hash_routes
  end

  # Two arguments used, so first specifies the namespace and the second specifies
  # the route name
  hash_branch('', "b") do |r|
    # uses :b as the namespace when looking up routes, as that was explicitly specified
    r.hash_routes(:b)
  end

  hash_path("/a", "/b") do |r|
    # /a/b path
  end

  hash_path("/a", "/c") do |r|
    # /a/c path
  end

  hash_path(:b, "/b") do |r|
    # /b/b path
  end

  hash_path(:b, "/c") do |r|
    # /b/c path
  end

  route do |r|
    # uses '' as the namespace, as no part of the path has been routed yet
    r.hash_branches
  end
end

With the above routing tree, requests for the /a and /b branches will be dispatched to the appropriate hash_branch block. Those blocks will the dispatch to the hash_path blocks, with the /a branch using the implicit namespace of /a, and the /b branch using the explicit namespace of :b. In general, it is best for performance to explicitly specify the namespace when calling r.hash_branches, r.hash_paths, and r.hash_routes.

Because specifying routes explicitly using the hash_branch and hash_path class methods can get repetitive, the hash_routes plugin offers a DSL for DRYing the code up. This DSL is used by calling the hash_routes class method. Below is a translation of the previous example to using the hash_routes DSL:

class App < Roda
  plugin :hash_routes

  # No block argument is used, DSL evaluates block using instance_exec
  hash_routes "" do
    # on method is used for routing to next segment,
    # for similarity to standard Roda
    on "a" do |r|
      r.hash_routes '/a'
    end

    on "b" do |r|
      r.hash_routes(:b)
    end
  end

  # Block argument is used, block is yielded DSL instance
  hash_routes "/a" do |hr|
    # is method is used for routing to the remaining path,
    # for similarity to standard Roda
    hr.is "b" do |r|
      # /a/b path
    end

    hr.is "c" do |r|
      # /a/c path
    end
  end

  hash_routes :b do
    is "b" do |r|
      # /b/b path
    end

    is "c" do |r|
      # /b/c path
    end
  end

  route do |r|
    # No change here, DSL only makes setup DRYer
    r.hash_branches
  end
end

The hash_routes DSL also offers some additional features to handle additional cases. It supports verb methods, such as get and post, which operate like is, but are only called if the verb matches (and are not yielded the request). It supports a view method for routes that only render views, as well as a views method for setting up routes for multiple views in a single call, which is a good replacement for the multi_view plugin. is, view, and the verb methods can use a value of true for the empty remaining path (as the empty string specifies the "/" remaining path). It also supports a dispatch_from method, allowing you to setup dispatching to current group of routes from a higher-level namespace. The hash_routes class method will return the DSL instance, so you are not limited to using it with a block.

Here's the above example modified to use some of these features:

class App < Roda
  plugin :hash_routes

  hash_routes "/a" do
    # Dispatch requests for the /a branch from the empty (default) routing
    # namespace to this namespace
    dispatch_from "a"

    # Handle GET /a path, render "a" template, returning 404 for non-GET requests
    view true, "a"

    # Handle /a/b path, returning 404 for non-GET requests
    get "b" do
      # GET /a/b path
    end

    # Handle /a/c path, returning 404 for non-POST requests
    post "c" do
      # POST /a/c path
    end
  end

  bhr = hash_routes(:b)

  # Dispatch requests for the /b branch from the empty routing to this namespace,
  # but first check routes in the :b_preauth namespace.  If there is no
  # matching route in the :b_preauth namespace, call the check_authenticated!
  # method before dispatching to any of the routes in this namespace
  bhr.dispatch_from "", "b" do |r|
    r.hash_routes :b_preauth
    check_authenticated!
  end

  bhr.is true do |r|
    # /b path
  end

  bhr.is "" do |r|
    # /b/ path
  end

  # GET /b/d path, render 'd2' template, returning 404 for non-GET requests
  bhr.views 'd', 'd2'

  # GET /b/e path, render 'e' template, returning 404 for non-GET requests
  # GET /b/f path, render 'f' template, returning 404 for non-GET requests
  bhr.views %w'e f'

  route do |r|
    r.hash_branches
  end
end

The view and views method depend on the render plugin being loaded, but this plugin does not load the render plugin. You must load the render plugin separately if you want to use the view and views methods.

Certain parts of the hash_routes DSL support do not work with the route_block_args plugin, as doing so would reduce performance. These are:

  • dispatch_from

  • view

  • views

  • all verb methods (get, post, etc.)

Methods

Public Class

  1. configure

Public Class methods

configure (app)
[show source]
    # File lib/roda/plugins/hash_routes.rb
267 def self.configure(app)
268   app.opts[:hash_branches] ||= {}
269   app.opts[:hash_paths] ||= {}
270   app.opts[:hash_routes_methods] ||= {}
271 end