class Rack::MatrixParams

Public Class Methods

new(app) click to toggle source
# File lib/sinatra/rack_matrix_params.rb, line 23
def initialize(app)
  @app = app
end

Public Instance Methods

call(env) click to toggle source

This will allow to use 'matrix' params in requests, like:

example.com/library;section=nw/books;topic=money;binding=hardcover

Will result in this params matrix:

> params['section'] = 'nw'

> params['topic'] = 'money'

> params['binding'] = 'hardcover'

All HTTP methods are supported, in case of POST they will be passed as a regular <form> parameters.

# File lib/sinatra/rack_matrix_params.rb, line 39
def call(env)

  # This ugly hack should fix the issue with Rack::Test where
  # these two variables are empty and Rack::Test will always
  # return 404.
  #
  if env['rack.test']
    env['REQUEST_URI'] = env['PATH_INFO']
    env['REQUEST_PATH'] = env['PATH_INFO']
  end

  # Split URI to components and then extract ;var=value pairs
  matrix_params = {}
  uri_components = (env['rack.test'] ? env['PATH_INFO'] : env['REQUEST_URI']).split('/')

  uri_components.each do |component|
    sub_components, value = component.split(/\;(\w+)\=/), nil
    next unless sub_components.first  # Skip subcomponent if it's empty (usually /)
    while param=sub_components.pop do
      if value
        matrix_params[sub_components.first] ||= {}
        matrix_params[sub_components.first].merge!(param => value)
        value=nil
        next
      else
        value = param.gsub(/\?.*$/, '')
      end
    end
  end

  # Two things need to happen to make matrix params work:
  #     (1) the parameters need to be appended to the 'normal' params
  #         for the request. 'Normal' really depends on the content
  #         type of the request, which does not seem accessible from
  #         Middleware, so we use the existence of
  #         rack.request.form_hash in the environment to distinguish
  #         between basic and application/x-www-form-urlencoded
  #         requests
  #     (2) the parameters need to be stripped from the appropriate
  #         path related env variables, so that request dispatching
  #         does not trip over them

  # (1) Rewrite current path by stripping all matrix params from it
  ['REQUEST_PATH', 'REQUEST_URI', 'PATH_INFO'].select { |k|
    env[k] }.each do |k|
    env[k] = env[k].remove_matrix_params
  end

  # (2) Append the matrix params to the 'normal' request params
  # FIXME: Make this work for multipart/form-data
  if env['rack.request.form_hash']
    # application/x-www-form-urlencoded, most likely a POST
    env['rack.request.form_hash'].merge!(matrix_params)
  else
    # For other methods it's more complicated
    if env['REQUEST_METHOD']!='POST' and not matrix_params.keys.empty?
      env['QUERY_STRING'] = env['QUERY_STRING'].gsub(/;([^\/]*)/, '')
      new_params = matrix_params.collect do |component, params|
        params.collect { |k,v| "#{component}[#{k}]=#{CGI::escape(v.to_s)}" }
      end.flatten
      # Add matrix params as a regular GET params
      env['QUERY_STRING'] += '&' if not env['QUERY_STRING'].empty?
      env['QUERY_STRING'] += "#{new_params.join('&')}"
    end
  end
  @app.call(env)
end