Skip to content
This repository
Browse code

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...
commit 88f2284b32b5cc302d2b37c0ad3c2667d4c58c6a 1 parent e085270
Jeremy Kemper authored October 11, 2007
2  actionpack/CHANGELOG
... ...
@@ -1,5 +1,7 @@
1 1
 *SVN*
2 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
+
3 5
 * Fix in place editor's setter action with non-string fields.  #7418 [Andreas]
4 6
 
5 7
 
34  actionpack/lib/action_controller/routing.rb
@@ -451,26 +451,17 @@ def extra_keys(hash, recall={})
451 451
       # is given (as an array), only the keys indicated will be used to build
452 452
       # the query string. The query string will correctly build array parameter
453 453
       # values.
454  
-      def build_query_string(hash, only_keys=nil)
  454
+      def build_query_string(hash, only_keys = nil)
455 455
         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     
469 456
         
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 * '&'}"
472 463
       end
473  
-  
  464
+
474 465
       # Write the real recognition implementation and then resend the message.
475 466
       def recognize(path, environment={})
476 467
         write_recognition
@@ -668,7 +659,7 @@ def local_name
668 659
       end
669 660
   
670 661
       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}"
672 663
       end
673 664
       def value_check
674 665
         if default # Then we know it won't be nil
@@ -1230,10 +1221,9 @@ def options_as_params(options)
1230 1221
         #
1231 1222
         # great fun, eh?
1232 1223
 
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]
1237 1227
         options_as_params
1238 1228
       end
1239 1229
   
2  actionpack/test/controller/routing_test.rb
@@ -946,7 +946,7 @@ def test_escape_spaces_build_query_string
946 946
   end
947 947
 
948 948
   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]))
950 950
   end
951 951
 
952 952
   def test_escape_spaces_build_query_string_selected_keys
60  actionpack/test/controller/url_rewriter_test.rb
@@ -23,15 +23,6 @@ def test_anchor
23 23
       @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
24 24
     )
25 25
   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
35 26
 end
36 27
 
37 28
 class UrlWriterTests < Test::Unit::TestCase
@@ -123,5 +114,54 @@ def test_only_path
123 114
   ensure
124 115
     ActionController::Routing::Routes.load!
125 116
   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
127 167
 end
5  activesupport/CHANGELOG
... ...
@@ -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
+
1 6
 *1.4.3* (October 4th, 2007)
2 7
 
3 8
 * Demote Hash#to_xml to use XmlSimple#xml_in_string so it can't read files or stdin.  #8453 [candlerb, Jeremy Kemper]
24  activesupport/lib/active_support/core_ext/hash/conversions.rb
... ...
@@ -1,5 +1,23 @@
1 1
 require 'date'
2 2
 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
3 21
 
4 22
 # Locked down XmlSimple#xml_in_string
5 23
 class XmlSimple
@@ -48,6 +66,12 @@ def self.included(klass)
48 66
           klass.extend(ClassMethods)
49 67
         end
50 68
 
  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
+
51 75
         def to_xml(options = {})
52 76
           options[:indent] ||= 2
53 77
           options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]),
37  activesupport/test/core_ext/hash_ext_test.rb
@@ -470,3 +470,40 @@ def test_kernel_method_names_to_xml
470 470
     end
471 471
   end
472 472
 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 notes on commit 88f2284

Please sign in to comment.
Something went wrong with that request. Please try again.