lib/roda/plugins/multi_run.rb



class Roda
  module RodaPlugins
    # The multi_run plugin provides the ability to easily dispatch to other
    # rack applications based on the request path prefix.
    # First, load the plugin:
    #
    #   class App < Roda
    #     plugin :multi_run
    #   end
    #
    # Then, other rack applications can register with the multi_run plugin:
    #
    #   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
    # 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.
    #
    # 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.
    module MultiRun
      # Initialize the storage for the dispatched applications
      def self.configure(app)
        app.instance_eval do
          @multi_run_apps ||= {}
        end
      end

      module ClassMethods
        # Hash storing rack applications to dispatch to, keyed by the prefix
        # for the application.
        attr_reader :multi_run_apps

        # Add a rack application to dispatch to for the given prefix when
        # r.multi_run is called.
        def run(prefix, app)
          @multi_run_apps[prefix.to_s] = app
          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)})/
        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.
        def multi_run
          on self.class.multi_run_regexp do |prefix|
            run scope.class.multi_run_apps[prefix]
          end
        end
      end
    end

    register_plugin(:multi_run, MultiRun)
  end
end