Search code examples
rubyhanamihanami-router

How to use lotus router with Rack::Builder::map


Is there a way to use map and the (lotus)router namespacing together? Below is a sample config.ru I'm trying to get running as a demo.

require 'bundler'
Bundler.require

module Demo

  class Application

    def initialize
      @app = Rack::Builder.new do
        map '/this_works' do
          run  Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["this_works"]]}
        end
        map '/api' do
          run Lotus::Router.new do
            get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
            get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
          end
        end
      end
    end

    def call(env)
      @app.call(env)
    end
  end  
end

run Demo::Application.new

Solution

  • Your problem is due to the precedence of do..end in method calls. In your code the section

    run Lotus::Router.new do
      get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
      get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
    end
    

    is parsed by Ruby as

    run(Lotus::Router.new) do
      get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
      get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
    end
    

    In other words the block is passed to run, not to Lotus::Router.new as you intended, and run simply ignores the block.

    To fix it you need to ensure the block is associated with the constructor of the router rather than the call to run. There are a couple of ways to do this. You could use {...} rather than do...end, as that has a higher precedence:

    run Lotus::Router.new {
      #...
    }
    

    An alternative would be to assign the router to a local variable, and use that as the argument to run:

    router = Lotus::Router.new do
      #...
    end
    run router