From 1fa9807f4b99e6b61e6111cf9e99b8255fb0d217 Mon Sep 17 00:00:00 2001 From: Nicolas Sanguinetti Date: Thu, 15 Jan 2009 04:24:45 -0800 Subject: [PATCH] Nested params (e.g., "post[title]=Hello") [#70] This is based largely on manveru's example implementation: http://paste.linuxhelp.tv/pastes/view/15309 NOTE: we should plan on ripping this out once nested params makes it into Rack. --- CHANGES | 4 ++++ lib/sinatra/base.rb | 22 +++++++++++++---- test/routing_test.rb | 57 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index ffdf2fbe8a..081568e129 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ Documentation on using these features is forth-coming; the following provides the basic gist: http://gist.github.com/38605 + * Parameters with subscripts are now parsed into a nested/recursive + Hash structure. e.g., "post[title]=Hello&post[body]=World" yields + params: {'post' => {'title' => 'Hello', 'body' => 'World'}}. + * Regular expressions may now be used in route pattens; captures are available at "params[:captures]". diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index 75d9069f67..6e5e6534f4 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -331,8 +331,7 @@ def dispatch! self.class.filters.each {|block| instance_eval(&block)} if routes = self.class.routes[@request.request_method] path = @request.path_info - original_params = Hash.new{ |hash,k| hash[k.to_s] if Symbol === k } - original_params.merge! @request.params + original_params = nested_params(@request.params) routes.each do |pattern, keys, conditions, method_name| if pattern =~ path @@ -352,8 +351,7 @@ def dispatch! else {} end - @params = original_params.dup - @params.merge!(params) + @params = original_params.merge(params) catch(:pass) { conditions.each { |cond| @@ -366,6 +364,22 @@ def dispatch! raise NotFound end + def nested_params(params) + return indifferent_hash.merge(params) if !params.keys.join.include?('[') + params.inject indifferent_hash do |res, (key,val)| + if key =~ /\[.*\]/ + splat = key.scan(/(^[^\[]+)|\[([^\]]+)\]/).flatten.compact + head, last = splat[0..-2], splat[-1] + head.inject(res){ |s,v| s[v] ||= indifferent_hash }[last] = val + end + res + end + end + + def indifferent_hash + Hash.new {|hash,key| hash[key.to_s] if Symbol === key } + end + def invoke(handler) res = catch(:halt) { if handler.respond_to?(:call) diff --git a/test/routing_test.rb b/test/routing_test.rb index 3a9135c048..b5ea6d22e7 100644 --- a/test/routing_test.rb +++ b/test/routing_test.rb @@ -119,6 +119,63 @@ assert ok? end + it "supports basic nested params" do + mock_app { + get '/hi' do + params["person"]["name"] + end + } + + get "/hi?person[name]=John+Doe" + assert ok? + assert_equal "John Doe", body + end + + it "exposes nested params with indifferent hash" do + mock_app { + get '/testme' do + assert_equal 'baz', params['bar']['foo'] + assert_equal 'baz', params['bar'][:foo] + 'well, alright' + end + } + get '/testme?bar[foo]=baz' + assert_equal 'well, alright', body + end + + it "supports deeply nested params" do + input = { + 'browser[chrome][engine][name]' => 'V8', + 'browser[chrome][engine][version]' => '1.0', + 'browser[firefox][engine][name]' => 'spidermonkey', + 'browser[firefox][engine][version]' => '1.7.0', + 'emacs[map][goto-line]' => 'M-g g', + 'emacs[version]' => '22.3.1', + 'paste[name]' => 'hello world', + 'paste[syntax]' => 'ruby' + } + expected = { + "emacs" => { + "map" => { "goto-line" => "M-g g" }, + "version" => "22.3.1" + }, + "browser" => { + "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}}, + "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}} + }, + "paste" => {"name"=>"hello world", "syntax"=>"ruby"} + } + mock_app { + get '/foo' do + assert_equal expected, params + 'looks good' + end + } + get "/foo?#{param_string(input)}" + assert ok? + assert_equal 'looks good', body + end + it "supports paths that include spaces" do mock_app { get '/path with spaces' do