lib/roda/plugins/multi_run.rb in roda-3.67.0 vs lib/roda/plugins/multi_run.rb in roda-3.68.0

- old
+ new

@@ -15,51 +15,74 @@ # # App.run "ra", PlainRackApp # App.run "ro", OtherRodaApp # App.run "si", SinatraApp # - # Inside your route block, you can call r.multi_run to dispatch to all + # Inside your route block, you can call +r.multi_run+ to dispatch to all # three rack applications based on the prefix: # # App.route do |r| # r.multi_run # end # # This will dispatch routes starting with +/ra+ to +PlainRackApp+, routes # starting with +/ro+ to +OtherRodaApp+, and routes starting with +/si+ to # SinatraApp. # - # You can pass a block to +multi_run+ that will be called with the prefix, + # You can pass a block to +r.multi_run+ that will be called with the prefix, # before dispatching to the rack app: # # App.route do |r| # r.multi_run do |prefix| # # do something based on prefix before the request is passed further # end # end # # This is useful for modifying the environment before passing it to the rack app. # - # The multi_run plugin is similar to the multi_route plugin, with the difference - # being the multi_route plugin keeps all routing subtrees in the same Roda app/class, - # while multi_run dispatches to other rack apps. If you want to isolate your routing - # subtrees, multi_run is a better approach, but it does not let you set instance - # variables in the main Roda app and have those instance variables usable in - # the routing subtrees. + # You can also call +Roda.run+ with a block: # + # App.run("ra"){PlainRackApp} + # App.run("ro"){OtherRodaApp} + # App.run("si"){SinatraApp} + # + # When called with a block, Roda will call the block to get the app to dispatch to + # every time the block is called. The expected usage is with autoloaded classes, + # so that the related classes are not loaded until there is a request for the + # related route. This can sigficantly speedup startup or testing a subset of the + # application. When freezing an application, the blocks are called once to get the + # app to dispatch to, and that is cached, to ensure the any autoloads are completed + # before the application is frozen. + # + # The multi_run plugin is similar to the hash_branches and multi_route plugins, with + # the difference being the hash_branches and multi_route plugins keep all routing + # subtrees in the same Roda app/class, while multi_run dispatches to other rack apps. + # If you want to isolate your routing subtrees, multi_run is a better approach, but + # it does not let you set instance variables in the main Roda app and have those + # instance variables usable in the routing subtrees. + # # To handle development environments that reload code, you can call the # +run+ class method without an app to remove dispatching for the prefix. module MultiRun # Initialize the storage for the dispatched applications def self.configure(app) app.opts[:multi_run_apps] ||= {} + app.opts[:multi_run_app_blocks] ||= {} end module ClassMethods + # Convert app blocks into apps by calling them, in order to force autoloads + # and to speed up subsequent calls. # Freeze the multi_run apps so that there can be no thread safety issues at runtime. def freeze - opts[:multi_run_apps].freeze + app_blocks = opts[:multi_run_app_blocks] + apps = opts[:multi_run_apps] + app_blocks.each do |prefix, block| + apps[prefix] = block.call + end + app_blocks.clear.freeze + apps.freeze self::RodaRequest.refresh_multi_run_regexp! super end # Hash storing rack applications to dispatch to, keyed by the prefix @@ -67,40 +90,51 @@ def multi_run_apps opts[:multi_run_apps] end # Add a rack application to dispatch to for the given prefix when - # r.multi_run is called. - def run(prefix, app=nil) - if app - multi_run_apps[prefix.to_s] = app + # r.multi_run is called. If a block is given, it is called every time + # there is a request for the route to get the app to call. If neither + # a block or an app is provided, any stored route for the prefix is + # removed. It is an error to provide both an app and block in the same call. + def run(prefix, app=nil, &block) + prefix = prefix.to_s + if app + raise Roda::RodaError, "cannot provide both app and block to Roda.run" if block + opts[:multi_run_apps][prefix] = app + opts[:multi_run_app_blocks].delete(prefix) + elsif block + opts[:multi_run_apps].delete(prefix) + opts[:multi_run_app_blocks][prefix] = block else - multi_run_apps.delete(prefix.to_s) + opts[:multi_run_apps].delete(prefix) + opts[:multi_run_app_blocks].delete(prefix) end self::RodaRequest.refresh_multi_run_regexp! end end module RequestClassMethods # Refresh the multi_run_regexp, using the stored route prefixes, # preferring longer routes before shorter routes. def refresh_multi_run_regexp! - @multi_run_regexp = /(#{Regexp.union(roda_class.multi_run_apps.keys.sort.reverse)})/ + @multi_run_regexp = /(#{Regexp.union((roda_class.opts[:multi_run_apps].keys + roda_class.opts[:multi_run_app_blocks].keys).sort.reverse)})/ end # Refresh the multi_run_regexp if it hasn't been loaded yet. def multi_run_regexp @multi_run_regexp || refresh_multi_run_regexp! end end module RequestMethods # If one of the stored route prefixes match the current request, - # dispatch the request to the stored rack application. + # dispatch the request to the appropriate rack application. def multi_run on self.class.multi_run_regexp do |prefix| yield prefix if defined?(yield) - run scope.class.multi_run_apps[prefix] + opts = scope.opts + run(opts[:multi_run_apps][prefix] || opts[:multi_run_app_blocks][prefix].call) end end end end