Skip to content

Commit

Permalink
Backport: allow array and hash query parameters. Array route paramete…
Browse files Browse the repository at this point in the history
…rs 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
  • Loading branch information
jeremy committed Oct 11, 2007
1 parent e085270 commit 88f2284
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 33 deletions.
2 changes: 2 additions & 0 deletions actionpack/CHANGELOG
Original file line number Diff line number Diff line change
@@ -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]


Expand Down
34 changes: 12 additions & 22 deletions actionpack/lib/action_controller/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion actionpack/test/controller/routing_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 50 additions & 10 deletions actionpack/test/controller/url_rewriter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
5 changes: 5 additions & 0 deletions activesupport/CHANGELOG
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
24 changes: 24 additions & 0 deletions activesupport/lib/active_support/core_ext/hash/conversions.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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]),
Expand Down
37 changes: 37 additions & 0 deletions activesupport/test/core_ext/hash_ext_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.