Search code examples
rubyrubygemssubdomainrackmiddleware

Modify Rack App


For one of my ruby applications i need the server to route requests based on the subdomain. There are ways to do this using other gems but i decided to make my own "middleware". This code runs applications based on where the request is going to.

config.ru

require './subdomain'
require './www'

run Rack::Subdomain.new([
  {
    :subdomain => "test", 
    :application => Sinatra::Application
  }
]);

subdomain.rb

module Rack
  class Subdomain
    def initialize(app)
      @app = app
    end

    def call(env)
      @app.each do |app|
        match = 'test'.match(app[:subdomain])
        if match
          return app[:application].call(env)
        end
      end
    end
  end
end

My question is how can i modify this working code to work exactly the same but have it called by code that looks like this:

run Rack::Subdomain do
  map 'www' do
    Example::WWW
  end

  map 'api' do
    Example::API
  end
end

With suggested code:

config.ru

require './subdomain'
require './www'

run Rack::Subdomain.new do |x|
  x.map 'test' do
    Sinatra::Application
  end
end

subdomain.rb

module Rack
  class Subdomain
    def initialize(routes = nil)
      @routes = routes
      yield self
    end

    def map(subdomain)
      @routes << { subdomain: subdomain, application: yield }
    end

    def call(env)
      @routes.each do |route|
        match = 'test'.match(route[:subdomain])
        if match
          return route[:application].call(env)
        end
      end
    end
  end
end

Solution

  • You call the above "working code" but it doesn't seem to detect the subdomain at all, but wires it to the literal 'test'. At any rate, you can implement a pattern similar to what you want by making a map method which adds entries to your list of subdomain->application routes. I've renamed your @app to @routes since it is a hash of routes, not an application reference.

    module Rack
      class Subdomain
        def initialize(routes = [])
          @routes = routes
          yield self if block_given?
        end
    
        def map(subdomain)
          @routes << { subdomain: subdomain, application: yield }
        end
    
        def call(env)
          @routes.each do |route|
            match = 'test'.match(route[:subdomain])
            if match
              return route[:application].call(env)
            end
          end
        end
      end
    end
    
    rsd = Rack::Subdomain.new do |x|
      x.map 'www' do
        Example::WWW
      end
    
      x.map 'api' do
        Example::API
      end
    end
    
    run rsd