lib/roda/plugins/hash_routes.rb in roda-3.56.0 vs lib/roda/plugins/hash_routes.rb in roda-3.57.0
- old
+ new
@@ -1,18 +1,14 @@
# frozen-string-literal: true
#
class Roda
module RodaPlugins
- # 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.
+ # The hash_routes plugin builds on top of the hash_branches and hash_paths plugins, and adds
+ # a DSL for configuring hash branches and paths. It also adds an +r.hash_routes+ method for
+ # first attempting dispatch to the configured hash_paths, then to the configured hash_branches:
#
- # For a basic replacement of the multi_route plugin, you can replace class level
- # <tt>route('segment')</tt> calls with <tt>hash_branch('segment')</tt>:
- #
# class App < Roda
# plugin :hash_routes
#
# hash_branch("a") do |r|
# # /a branch
@@ -20,126 +16,35 @@
#
# 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:
+ # It is best for performance to explicitly specify the namespace when calling
+ # +r.hash_routes+.
#
- # 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:
+ # the code up. This DSL is used by calling the +hash_routes+ class method. The
+ # DSL used tries to mirror the standard Roda DSL, but it is not a normal routing
+ # tree (it's not possible to execute arbitrary code between branches during routing).
#
# class App < Roda
# plugin :hash_routes
#
# # No block argument is used, DSL evaluates block using instance_exec
@@ -262,13 +167,16 @@
# * dispatch_from
# * view
# * views
# * all verb methods (get, post, etc.)
module HashRoutes
+ def self.load_dependencies(app)
+ app.plugin :hash_branches
+ app.plugin :hash_paths
+ end
+
def self.configure(app)
- app.opts[:hash_branches] ||= {}
- app.opts[:hash_paths] ||= {}
app.opts[:hash_routes_methods] ||= {}
end
# Internal class handling the internals of the +hash_routes+ class method blocks.
class DSL
@@ -357,28 +265,14 @@
end
module ClassMethods
# Freeze the hash_routes metadata when freezing the app.
def freeze
- opts[:hash_branches].freeze.each_value(&:freeze)
- opts[:hash_paths].freeze.each_value(&:freeze)
opts[:hash_routes_methods].freeze
super
end
- # Duplicate hash_routes metadata in subclass.
- def inherited(subclass)
- super
-
- [:hash_branches, :hash_paths].each do |k|
- h = subclass.opts[k]
- opts[k].each do |namespace, routes|
- h[namespace] = routes.dup
- end
- end
- end
-
# Invoke the DSL for configuring hash routes, see DSL for methods inside the
# block. If the block accepts an argument, yield the DSL instance. If the
# block does not accept an argument, instance_exec the block in the context
# of the DSL instance.
def hash_routes(namespace='', &block)
@@ -391,69 +285,12 @@
end
end
dsl
end
-
- # Add branch handler for the given namespace and segment. If called without
- # a block, removes the existing branch handler if it exists.
- def hash_branch(namespace='', segment, &block)
- segment = "/#{segment}"
- routes = opts[:hash_branches][namespace] ||= {}
- if block
- routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
- elsif meth = routes[segment]
- routes.delete(segment)
- remove_method(meth)
- end
- end
-
- # Add path handler for the given namespace and path. When the
- # r.hash_paths method is called, checks the matching namespace
- # for the full remaining path, and dispatch to that block if
- # there is one. If called without a block, removes the existing
- # path handler if it exists.
- def hash_path(namespace='', path, &block)
- routes = opts[:hash_paths][namespace] ||= {}
- if block
- routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
- elsif meth = routes[path]
- routes.delete(path)
- remove_method(meth)
- end
- end
end
module RequestMethods
- # Checks the matching hash_branch namespace for a branch matching the next
- # segment in the remaining path, and dispatch to that block if there is one.
- def hash_branches(namespace=matched_path)
- rp = @remaining_path
-
- return unless rp.getbyte(0) == 47 # "/"
-
- if routes = roda_class.opts[:hash_branches][namespace]
- if segment_end = rp.index('/', 1)
- if meth = routes[rp[0, segment_end]]
- @remaining_path = rp[segment_end, 100000000]
- always{scope.send(meth, self)}
- end
- elsif meth = routes[rp]
- @remaining_path = ''
- always{scope.send(meth, self)}
- end
- end
- end
-
- # Checks the matching hash_path namespace for a branch matching the
- # remaining path, and dispatch to that block if there is one.
- def hash_paths(namespace=matched_path)
- if (routes = roda_class.opts[:hash_paths][namespace]) && (meth = routes[@remaining_path])
- @remaining_path = ''
- always{scope.send(meth, self)}
- end
- end
-
# Check for matches in both the hash_path and hash_branch namespaces for
# a matching remaining path or next segment in the remaining path, respectively.
def hash_routes(namespace=matched_path)
hash_paths(namespace)
hash_branches(namespace)