Poul Poul - 1 month ago 13
Ruby Question

ruby rackup: is it possible to programatically add a mapping from within another mapping?

I have a .ru file and can set up mappings without issue ( the 'register' mapping below ).

However I want services to be able to register themselves by hitting a url so I want to be able to add new mappings on the fly from within others mappings.

The below code does not work though. What am I doing wrong and is this possible?

Thanks!

map '/register' do
run Proc.new { |env|
# inside of register i want to add another mapping.
# obviously 'bar' would be a value read out of env
map '/bar' do
run Proc.new{ |env| ['200', { 'Content-Type' => 'text/html' },'bar' }
end

[ '200', {'Content-Type' => 'text/html'}, "registered"]
}
end

Answer

I don't think there's a way to add routes after-the-fact using map. One alternative is to use Rack::URLMap to define your app. You'll need to maintain your own list of registered routes (as a hash) and call Rack::URLMap#remap every time you add a new route to the hash:

url_map = Rack::URLMap.new
routes = {
  "/register" => lambda do |env|
    routes["/bar"] = lambda do |env|
      [ "200", {"Content-Type" => "text/plain"}, ["bar"] ]
    end

    url_map.remap(routes)

    [ "200", {"Content-Type" => "text/plain"}, ["registered"] ]
  end
}

url_map.remap(routes)
run url_map

Note that you could do with with just the hash, but URLMap provides some nice conveniences, including 404 handling. It's actually a really nice little class and worth reading if you have five minutes to spare.

If you were so inclined, you could turn this into a tidy little class:

class Application
  def initialize
    @routes = {}
    @url_map = Rack::URLMap.new

    register_route "/register" do |env|
      # When "/register" is requested, register the new route "/bar"
      register_route "/bar" do |env|
        [ 200, {"Content-Type" => "text/plain"}, ["bar"] ]
      end
      [ 200, {"Content-Type" => "text/plain"}, ["registered"] ]
    end
  end

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

  private
  def register_route(path, &block)
    @routes[path] = block
    @url_map.remap(@routes)
  end
end

run Application.new