Permalink
Browse files

Backport: allow array and hash query parameters. Array route paramete…

…rs are converted/to/a/path as before. Closes #7047.

git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-stable@7834 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
jeremy committed Oct 11, 2007
1 parent e085270 commit 88f2284b32b5cc302d2b37c0ad3c2667d4c58c6a
View
@@ -1,5 +1,7 @@
*SVN*
+* Backport: allow array and hash query parameters. Array route parameters are converted/to/a/path as before. #6765, #7047, #7462 [bgipsy, Jeremy McAnally, Dan Kubb, brendan, Diego Algorta Casamayou]
+
* Fix in place editor's setter action with non-string fields. #7418 [Andreas]
@@ -451,26 +451,17 @@ def extra_keys(hash, recall={})
# is given (as an array), only the keys indicated will be used to build
# the query string. The query string will correctly build array parameter
# values.
- def build_query_string(hash, only_keys=nil)
+ def build_query_string(hash, only_keys = nil)
elements = []
-
- only_keys ||= hash.keys
-
- only_keys.each do |key|
- value = hash[key] or next
- key = CGI.escape key.to_s
- if value.class == Array
- key << '[]'
- else
- value = [ value ]
- end
- value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" }
- end
- query_string = "?#{elements.join("&")}" unless elements.empty?
- query_string || ""
+ (only_keys || hash.keys).each do |key|
+ if value = hash[key]
+ elements << value.to_query(key)
+ end
+ end
+ elements.empty? ? '' : "?#{elements.sort * '&'}"
end
-
+
# Write the real recognition implementation and then resend the message.
def recognize(path, environment={})
write_recognition
@@ -668,7 +659,7 @@ def local_name
end
def extract_value
- "#{local_name} = hash[:#{key}] #{"|| #{default.inspect}" if default}"
+ "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
end
def value_check
if default # Then we know it won't be nil
@@ -1230,10 +1221,9 @@ def options_as_params(options)
#
# great fun, eh?
- options_as_params = options[:controller] ? { :action => "index" } : {}
- options.each do |k, value|
- options_as_params[k] = value.to_param
- end
+ options_as_params = options.clone
+ options_as_params[:action] ||= 'index' if options[:controller]
+ options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
options_as_params
end
@@ -946,7 +946,7 @@ def test_escape_spaces_build_query_string
end
def test_expand_array_build_query_string
- assert_equal '?x[]=1&x[]=2', order_query_string(@route.build_query_string(:x => [1, 2]))
+ assert_equal '?x%5B%5D=1&x%5B%5D=2', order_query_string(@route.build_query_string(:x => [1, 2]))
end
def test_escape_spaces_build_query_string_selected_keys
@@ -23,15 +23,6 @@ def test_anchor
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
)
end
-
- private
- def split_query_string(str)
- [str[0].chr] + str[1..-1].split(/&/).sort
- end
-
- def assert_query_equal(q1, q2)
- assert_equal(split_query_string(q1), split_query_string(q2))
- end
end
class UrlWriterTests < Test::Unit::TestCase
@@ -123,5 +114,54 @@ def test_only_path
ensure
ActionController::Routing::Routes.load!
end
-
+
+ def test_one_parameter
+ assert_equal('/c/a?param=val',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
+ )
+ end
+
+ def test_two_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
+ params = extract_params(url)
+ assert_equal params[0], { :p1 => 'X1' }.to_query
+ assert_equal params[1], { :p2 => 'Y2' }.to_query
+ end
+
+ def test_hash_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[category]' => 'prof' }.to_query
+ assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
+ end
+
+ def test_array_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
+ params = extract_params(url)
+ assert_equal params[0], { 'query[]' => 'Bob' }.to_query
+ assert_equal params[1], { 'query[]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_and_array_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
+ assert_match %r(^/c/a/101), url
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
+ assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
+ end
+
+ private
+ def extract_params(url)
+ url.split('?', 2).last.split('&')
+ end
end
View
@@ -1,3 +1,8 @@
+*SVN*
+
+* Backport: allow array and hash query parameters. Array route parameters are converted/to/a/path as before. #6765, #7047, #7462 [bgipsy, Jeremy McAnally, Dan Kubb, brendan, Diego Algorta Casamayou]
+
+
*1.4.3* (October 4th, 2007)
* Demote Hash#to_xml to use XmlSimple#xml_in_string so it can't read files or stdin. #8453 [candlerb, Jeremy Kemper]
@@ -1,5 +1,23 @@
require 'date'
require 'xml_simple'
+require 'cgi'
+
+# Extensions needed for Hash#to_query
+class Object
+ def to_param #:nodoc:
+ to_s
+ end
+
+ def to_query(key) #:nodoc:
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}"
+ end
+end
+
+class Array
+ def to_query(key) #:nodoc:
+ collect { |value| value.to_query("#{key}[]") }.sort * '&'
+ end
+end
# Locked down XmlSimple#xml_in_string
class XmlSimple
@@ -48,6 +66,12 @@ def self.included(klass)
klass.extend(ClassMethods)
end
+ def to_query(namespace = nil)
+ collect do |key, value|
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
+ end.sort * '&'
+ end
+
def to_xml(options = {})
options[:indent] ||= 2
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
@@ -470,3 +470,40 @@ def test_kernel_method_names_to_xml
end
end
end
+
+class QueryTest < Test::Unit::TestCase
+ def test_simple_conversion
+ assert_query_equal 'a=10', :a => 10
+ end
+
+ def test_cgi_escaping
+ assert_query_equal 'a%3Ab=c+d', 'a:b' => 'c d'
+ end
+
+ def test_nil_parameter_value
+ empty = Object.new
+ def empty.to_param; nil end
+ assert_query_equal 'a=', 'a' => empty
+ end
+
+ def test_nested_conversion
+ assert_query_equal 'person%5Bname%5D=Nicholas&person%5Blogin%5D=seckar',
+ :person => {:name => 'Nicholas', :login => 'seckar'}
+ end
+
+ def test_multiple_nested
+ assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10',
+ :person => {:id => 10}, :account => {:person => {:id => 20}}
+ end
+
+ def test_array_values
+ assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20',
+ :person => {:id => [10, 20]}
+ end
+
+ private
+ def assert_query_equal(expected, actual, message = nil)
+ assert_equal expected.split('&').sort, actual.to_query.split('&').sort
+ end
+end
+

0 comments on commit 88f2284

Please sign in to comment.