Skip to content

Commit 88f2284

Browse files
committed
Backport: allow array and hash query parameters. Array route parameters are converted/to/a/path as before. Closes rails#7047.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-stable@7834 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
1 parent e085270 commit 88f2284

File tree

7 files changed

+131
-33
lines changed

7 files changed

+131
-33
lines changed

actionpack/CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
*SVN*
22

3+
* 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]
4+
35
* Fix in place editor's setter action with non-string fields. #7418 [Andreas]
46

57

actionpack/lib/action_controller/routing.rb

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -451,26 +451,17 @@ def extra_keys(hash, recall={})
451451
# is given (as an array), only the keys indicated will be used to build
452452
# the query string. The query string will correctly build array parameter
453453
# values.
454-
def build_query_string(hash, only_keys=nil)
454+
def build_query_string(hash, only_keys = nil)
455455
elements = []
456-
457-
only_keys ||= hash.keys
458-
459-
only_keys.each do |key|
460-
value = hash[key] or next
461-
key = CGI.escape key.to_s
462-
if value.class == Array
463-
key << '[]'
464-
else
465-
value = [ value ]
466-
end
467-
value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" }
468-
end
469456

470-
query_string = "?#{elements.join("&")}" unless elements.empty?
471-
query_string || ""
457+
(only_keys || hash.keys).each do |key|
458+
if value = hash[key]
459+
elements << value.to_query(key)
460+
end
461+
end
462+
elements.empty? ? '' : "?#{elements.sort * '&'}"
472463
end
473-
464+
474465
# Write the real recognition implementation and then resend the message.
475466
def recognize(path, environment={})
476467
write_recognition
@@ -668,7 +659,7 @@ def local_name
668659
end
669660

670661
def extract_value
671-
"#{local_name} = hash[:#{key}] #{"|| #{default.inspect}" if default}"
662+
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
672663
end
673664
def value_check
674665
if default # Then we know it won't be nil
@@ -1230,10 +1221,9 @@ def options_as_params(options)
12301221
#
12311222
# great fun, eh?
12321223

1233-
options_as_params = options[:controller] ? { :action => "index" } : {}
1234-
options.each do |k, value|
1235-
options_as_params[k] = value.to_param
1236-
end
1224+
options_as_params = options.clone
1225+
options_as_params[:action] ||= 'index' if options[:controller]
1226+
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
12371227
options_as_params
12381228
end
12391229

actionpack/test/controller/routing_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ def test_escape_spaces_build_query_string
946946
end
947947

948948
def test_expand_array_build_query_string
949-
assert_equal '?x[]=1&x[]=2', order_query_string(@route.build_query_string(:x => [1, 2]))
949+
assert_equal '?x%5B%5D=1&x%5B%5D=2', order_query_string(@route.build_query_string(:x => [1, 2]))
950950
end
951951

952952
def test_escape_spaces_build_query_string_selected_keys

actionpack/test/controller/url_rewriter_test.rb

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@ def test_anchor
2323
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
2424
)
2525
end
26-
27-
private
28-
def split_query_string(str)
29-
[str[0].chr] + str[1..-1].split(/&/).sort
30-
end
31-
32-
def assert_query_equal(q1, q2)
33-
assert_equal(split_query_string(q1), split_query_string(q2))
34-
end
3526
end
3627

3728
class UrlWriterTests < Test::Unit::TestCase
@@ -123,5 +114,54 @@ def test_only_path
123114
ensure
124115
ActionController::Routing::Routes.load!
125116
end
126-
117+
118+
def test_one_parameter
119+
assert_equal('/c/a?param=val',
120+
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
121+
)
122+
end
123+
124+
def test_two_parameters
125+
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
126+
params = extract_params(url)
127+
assert_equal params[0], { :p1 => 'X1' }.to_query
128+
assert_equal params[1], { :p2 => 'Y2' }.to_query
129+
end
130+
131+
def test_hash_parameter
132+
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
133+
params = extract_params(url)
134+
assert_equal params[0], { 'query[category]' => 'prof' }.to_query
135+
assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
136+
end
137+
138+
def test_array_parameter
139+
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
140+
params = extract_params(url)
141+
assert_equal params[0], { 'query[]' => 'Bob' }.to_query
142+
assert_equal params[1], { 'query[]' => 'prof' }.to_query
143+
end
144+
145+
def test_hash_recursive_parameters
146+
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
147+
params = extract_params(url)
148+
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
149+
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
150+
assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
151+
end
152+
153+
def test_hash_recursive_and_array_parameters
154+
url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
155+
assert_match %r(^/c/a/101), url
156+
params = extract_params(url)
157+
assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
158+
assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
159+
assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
160+
assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
161+
end
162+
163+
private
164+
def extract_params(url)
165+
url.split('?', 2).last.split('&')
166+
end
127167
end

activesupport/CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
*SVN*
2+
3+
* 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]
4+
5+
16
*1.4.3* (October 4th, 2007)
27

38
* Demote Hash#to_xml to use XmlSimple#xml_in_string so it can't read files or stdin. #8453 [candlerb, Jeremy Kemper]

activesupport/lib/active_support/core_ext/hash/conversions.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
require 'date'
22
require 'xml_simple'
3+
require 'cgi'
4+
5+
# Extensions needed for Hash#to_query
6+
class Object
7+
def to_param #:nodoc:
8+
to_s
9+
end
10+
11+
def to_query(key) #:nodoc:
12+
"#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}"
13+
end
14+
end
15+
16+
class Array
17+
def to_query(key) #:nodoc:
18+
collect { |value| value.to_query("#{key}[]") }.sort * '&'
19+
end
20+
end
321

422
# Locked down XmlSimple#xml_in_string
523
class XmlSimple
@@ -48,6 +66,12 @@ def self.included(klass)
4866
klass.extend(ClassMethods)
4967
end
5068

69+
def to_query(namespace = nil)
70+
collect do |key, value|
71+
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
72+
end.sort * '&'
73+
end
74+
5175
def to_xml(options = {})
5276
options[:indent] ||= 2
5377
options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),

activesupport/test/core_ext/hash_ext_test.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,40 @@ def test_kernel_method_names_to_xml
470470
end
471471
end
472472
end
473+
474+
class QueryTest < Test::Unit::TestCase
475+
def test_simple_conversion
476+
assert_query_equal 'a=10', :a => 10
477+
end
478+
479+
def test_cgi_escaping
480+
assert_query_equal 'a%3Ab=c+d', 'a:b' => 'c d'
481+
end
482+
483+
def test_nil_parameter_value
484+
empty = Object.new
485+
def empty.to_param; nil end
486+
assert_query_equal 'a=', 'a' => empty
487+
end
488+
489+
def test_nested_conversion
490+
assert_query_equal 'person%5Bname%5D=Nicholas&person%5Blogin%5D=seckar',
491+
:person => {:name => 'Nicholas', :login => 'seckar'}
492+
end
493+
494+
def test_multiple_nested
495+
assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10',
496+
:person => {:id => 10}, :account => {:person => {:id => 20}}
497+
end
498+
499+
def test_array_values
500+
assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20',
501+
:person => {:id => [10, 20]}
502+
end
503+
504+
private
505+
def assert_query_equal(expected, actual, message = nil)
506+
assert_equal expected.split('&').sort, actual.to_query.split('&').sort
507+
end
508+
end
509+

0 commit comments

Comments
 (0)