Ruby: Làm thế nào để biến một hàm băm thành các tham số HTTP?


205

Điều đó khá dễ dàng với hàm băm đơn giản như

{:a => "a", :b => "b"} 

mà sẽ dịch sang

"a=a&b=b"

Nhưng bạn sẽ làm gì với thứ gì đó phức tạp hơn như

{:a => "a", :b => ["c", "d", "e"]} 

cái nào nên dịch thành

"a=a&b[0]=c&b[1]=d&b[2]=e" 

Hoặc thậm chí tệ hơn, (phải làm gì) với một cái gì đó như:

{:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]

Cảm ơn sự giúp đỡ đánh giá cao với điều đó!


Có vẻ như bạn muốn chuyển đổi JSON thành các thông số HTTP ... có lẽ bạn cần một mã hóa khác?
CookieOfFortune

Hum, đây thực sự không phải là Json, mà là Ruby Hash ... không chắc tôi hiểu tại sao mã hóa lại quan trọng ở đây.
Julien Genestoux

Câu trả lời của lmanners nên được thúc đẩy. Có rất nhiều câu trả lời tuyệt vời của riêng bạn ở đây (nhiều câu có điểm cao) nhưng ActiveSupport đã thêm hỗ trợ tiêu chuẩn cho việc này, kết xuất cuộc trò chuyện. Thật không may, câu trả lời của lmanner vẫn bị chôn vùi trong danh sách.
Noach Magedman

2
@Không theo ý kiến ​​của tôi, bất kỳ câu trả lời nào nói rằng dựa vào một thư viện mà rất nhiều lớp lõi khỉ nên vẫn bị chôn vùi. Sự biện minh cho một số lượng lớn các bản vá đó là không ổn định (hãy xem ý kiến ​​của Yehuda Katz trong bài viết này ), đây là một ví dụ tuyệt vời. YMMV, nhưng đối với tôi, một cái gì đó với phương thức lớp hoặc không mở Object và Hash, và nơi các tác giả sẽ không nói "chỉ không đụng độ với chúng tôi!" sẽ tốt hơn nhiều
Iain

Câu trả lời:


86

Cập nhật: Chức năng này đã bị xóa khỏi đá quý.

Julien, câu trả lời tự trả lời của bạn là một câu trả lời hay và tôi đã không biết xấu hổ mượn nó, nhưng nó không thoát khỏi các ký tự dành riêng, và có một vài trường hợp khác bị phá vỡ.

require "addressable/uri"
uri = Addressable::URI.new
uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
uri.query
# => "a=a&b[0]=c&b[1]=d&b[2]=e"
uri.query_values = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}
uri.query
# => "a=a&b[0][c]=c&b[0][d]=d&b[1][e]=e&b[1][f]=f"
uri.query_values = {:a => "a", :b => {:c => "c", :d => "d"}}
uri.query
# => "a=a&b[c]=c&b[d]=d"
uri.query_values = {:a => "a", :b => {:c => "c", :d => true}}
uri.query
# => "a=a&b[c]=c&b[d]"
uri.query_values = {:a => "a", :b => {:c => "c", :d => true}, :e => []}
uri.query
# => "a=a&b[c]=c&b[d]"

Đá quý là ' địa chỉ '

gem install addressable

1
Cám ơn! Các trường hợp cạnh mà giải pháp của tôi phá vỡ là gì? Vì vậy, tôi có thể thêm nó vào thông số kỹ thuật?
Julien Genestoux

2
Nó không xử lý booleans và điều này rõ ràng là không mong muốn: {"a" => "a & b = b"}. To_params
Bob Aman

3
Thật không may, hành vi này đã bị xóa khỏi Địa chỉ kể từ 2.3 ( github.com/sporkmonger/addressable/commit/
mẹo

2
@oif_vet Bạn có thể nói hành vi nào đã bị xóa không? Thẩm định đề xuất của Bob về việc sử dụng đá quý có thể đánh địa chỉ để giải quyết vấn đề của người gửi ban đầu đối với tôi theo địa chỉ-2.3.2.
sheldonh

1
@sheldonh, không, @oif_vet là chính xác. Tôi đã loại bỏ hành vi này. Các cấu trúc lồng nhau sâu không còn được hỗ trợ trong Địa chỉ làm đầu vào cho trình query_valuesbiến đổi.
Bob Aman

268

Đối với các băm cơ bản, không lồng nhau, Rails / ActiveSupport có Object#to_query.

>> {:a => "a", :b => ["c", "d", "e"]}.to_query
=> "a=a&b%5B%5D=c&b%5B%5D=d&b%5B%5D=e"
>> CGI.unescape({:a => "a", :b => ["c", "d", "e"]}.to_query)
=> "a=a&b[]=c&b[]=d&b[]=e"

http://api.rubyonrails.org/groupes/Object.html#method-i-to_query


1
Tại sao bạn nói nó bị hỏng? đầu ra bạn hiển thị là ok, phải không?
tokland

Tôi chỉ thử nó và bạn có vẻ đúng. Có thể câu lệnh của tôi ban đầu là do cách một phiên bản đường ray trước đó phân tích chuỗi truy vấn (tôi dường như nhớ lại nó ghi đè lên các giá trị 'b' trước đó). Đã bắt đầu NHẬN "/? Tham số HTML: {"a" => "a", "b" => ["c", "d", "e"]}
Gabe Martin-Dempesy

Điều gì sai nếu có băm lồng nhau? Tại sao tôi không thể sử dụng cái này khi có băm lồng nhau? Đối với tôi, nó chỉ là url thoát khỏi hàm băm lồng nhau, sẽ không có vấn đề gì khi sử dụng cái này trong yêu cầu http.
Sam

2
Không có đường ray: require 'active_support/all'là cần thiết
Dorian

Ít nhất với Rails 5.2 to_querykhông xử lý đúng giá trị nil. { a: nil, b: '1'}.to_query == "a=&b=1", nhưng Rack và CGI đều phân tích a=thành một chuỗi rỗng, không nil. Tôi không chắc chắn về việc hỗ trợ cho các máy chủ khác, nhưng với đường ray, chuỗi truy vấn chính xác phải là a&b=1. Tôi nghĩ thật sai lầm khi Rails không thể tạo ra một chuỗi truy vấn được phân tích chính xác bởi chính nó ...
jsmartt

153

Nếu bạn đang sử dụng Ruby 1.9.2 trở lên, bạn có thể sử dụng URI.encode_www_formnếu bạn không cần mảng.

Ví dụ: (từ các tài liệu Ruby trong 1.9.3):

URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => "ruby", "lang" => "en")
#=> "q=ruby&lang=en"
URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
#=> "q=ruby&q=perl&lang=en"
URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
#=> "q=ruby&q=perl&lang=en"

Bạn sẽ nhận thấy rằng các giá trị mảng không được đặt với các tên chính chứa []như tất cả chúng ta đã quen với các chuỗi truy vấn. Thông số kỹ thuật encode_www_formsử dụng phù hợp với định nghĩa application/x-www-form-urlencodeddữ liệu HTML5 .


8
+1, đây là tốt nhất. Nó không phụ thuộc vào bất kỳ nguồn nào bên ngoài Ruby.
Danyel

+1 hoạt động tốt với ví dụ '{: a => "a" ,: b => {: c => "c" ,: d => true} ,: e => []}'
Duke

1
Dường như không hoạt động với ruby ​​2.0 - hàm băm {:c => "c", :d => true}dường như được kiểm tra, do đó được gửi qua dưới dạng chuỗi.
dùng208769

1
Đó là một phần của đoạn trích lớn hơn ở trên -ruby -ruri -e 'puts RUBY_VERSION; puts URI.encode_www_form({:a => "a", :b => {:c => "c", :d => true}, :e => []})' # outputs 2.0.0 a=a&b=%7B%3Ac%3D%3E%22c%22%2C+%3Ad%3D%3Etrue%7D&
user208769 16/12/13

1
Lưu ý rằng điều này có kết quả khác nhau cho các giá trị mảng so với cả hai Addressable::URIvà ActiveSupport Object#to_query.
Matt Huggins

61

Không cần phải tải lên ActiveSupport cồng kềnh hoặc tự cuộn, bạn có thể sử dụng Rack::Utils.build_queryRack::Utils.build_nested_query. Đây là một bài đăng blog đưa ra một ví dụ tốt:

require 'rack'

Rack::Utils.build_query(
  authorization_token: "foo",
  access_level: "moderator",
  previous: "index"
)

# => "authorization_token=foo&access_level=moderator&previous=index"

Nó thậm chí còn xử lý các mảng:

Rack::Utils.build_query( {:a => "a", :b => ["c", "d", "e"]} )
# => "a=a&b=c&b=d&b=e"
Rack::Utils.parse_query _
# => {"a"=>"a", "b"=>["c", "d", "e"]}

Hoặc những thứ lồng nhau khó khăn hơn:

Rack::Utils.build_nested_query( {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}] } )
# => "a=a&b[][c]=c&b[][d]=d&b[][e]=e&b[][f]=f"
Rack::Utils.parse_nested_query _
# => {"a"=>"a", "b"=>[{"c"=>"c", "d"=>"d", "e"=>"e", "f"=>"f"}]}

Ví dụ lồng nhau của bạn chứng minh rằng nó không hoạt động đúng - khi bạn bắt đầu, :blà một mảng gồm hai giá trị băm. Bạn kết thúc với :bmột mảng của một hàm băm lớn hơn.
Ed Ruder

3
@EdRuder không đúng vì không có tiêu chuẩn được chấp nhận. Những gì nó thể hiện là nó gần hơn nhiều so với nỗ lực của bất kỳ ai khác, đánh giá bằng các câu trả lời khác.
iain

1
Phương pháp này không được chấp nhận kể từ Rails 2.3.8: apidock.com/rails/Rack/Utils/build_query
davidgoli

8
@davidgoli Erm, không có trong Rack, nó không phải là github.com/rack/rack/blob/1.5.2/lib/rack/utils.rb#L140 . Nếu bạn muốn sử dụng nó trong Rails, chắc chắn nó đơn giản như require 'rack'? Nó phải ở đó, xem xét tất cả các khung web chính của Ruby được xây dựng trên đỉnh Rack ngay bây giờ.
Iain

@EdRuder ActiveSupport's to_querycũng hợp nhất 2 mảng (v4.2).
Kelvin

9

Ăn cắp từ Merb:

# File merb/core_ext/hash.rb, line 87
def to_params
  params = ''
  stack = []

  each do |k, v|
    if v.is_a?(Hash)
      stack << [k,v]
    else
      params << "#{k}=#{v}&"
    end
  end

  stack.each do |parent, hash|
    hash.each do |k, v|
      if v.is_a?(Hash)
        stack << ["#{parent}[#{k}]", v]
      else
        params << "#{parent}[#{k}]=#{v}&"
      end
    end
  end

  params.chop! # trailing &
  params
end

Xem http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html


1
Thật không may, điều này không hoạt động khi chúng ta có một Mảng lồng nhau bên trong các thông số (xem ví dụ # 2) ... :(
Julien Genestoux

2
Và không làm bất cứ vị vua nào trốn thoát.
Ernest

9

Đây là một lớp lót ngắn và ngọt ngào nếu bạn chỉ cần hỗ trợ các chuỗi truy vấn khóa / giá trị ASCII đơn giản:

hash = {"foo" => "bar", "fooz" => 123}
# => {"foo"=>"bar", "fooz"=>123}
query_string = hash.to_a.map { |x| "#{x[0]}=#{x[1]}" }.join("&")
# => "foo=bar&fooz=123"

4
class Hash
  def to_params
    params = ''
    stack = []

    each do |k, v|
      if v.is_a?(Hash)
        stack << [k,v]
      elsif v.is_a?(Array)
        stack << [k,Hash.from_array(v)]
      else
        params << "#{k}=#{v}&"
      end
    end

    stack.each do |parent, hash|
      hash.each do |k, v|
        if v.is_a?(Hash)
          stack << ["#{parent}[#{k}]", v]
        else
          params << "#{parent}[#{k}]=#{v}&"
        end
      end
    end

    params.chop! 
    params
  end

  def self.from_array(array = [])
    h = Hash.new
    array.size.times do |t|
      h[t] = array[t]
    end
    h
  end

end

3
{:a=>"a", :b=>"b", :c=>"c"}.map{ |x,v| "#{x}=#{v}" }.reduce{|x,v| "#{x}&#{v}" }

"a=a&b=b&c=c"

Đây là một cách khác. Đối với các truy vấn đơn giản.


2
bạn thực sự phải đảm bảo rằng bạn đang thoát URI đúng cách và các giá trị của bạn. Ngay cả đối với những trường hợp đơn giản. Nó sẽ cắn bạn.
jrochkind

2

Tôi biết đây là một câu hỏi cũ, nhưng tôi chỉ muốn đăng đoạn mã này vì tôi không thể tìm thấy một viên ngọc đơn giản để thực hiện nhiệm vụ này cho tôi.

module QueryParams

  def self.encode(value, key = nil)
    case value
    when Hash  then value.map { |k,v| encode(v, append_key(key,k)) }.join('&')
    when Array then value.map { |v| encode(v, "#{key}[]") }.join('&')
    when nil   then ''
    else            
      "#{key}=#{CGI.escape(value.to_s)}" 
    end
  end

  private

  def self.append_key(root_key, key)
    root_key.nil? ? key : "#{root_key}[#{key.to_s}]"
  end
end

Cuộn lên như đá quý ở đây: https://github.com/simen/queryparams


1
URI.escape != CGI.escapevà cho URL bạn muốn cái đầu tiên.
Ernest

2
Trên thực tế không, @Ernest. Khi ví dụ: nhúng một url khác làm tham số cho url của bạn (giả sử đây là url trả về sẽ được chuyển hướng đến sau khi đăng nhập) URI.escape sẽ giữ nguyên '?' và '&' của url được nhúng tại chỗ phá vỡ url xung quanh, trong khi CGI.escape sẽ giấu chúng chính xác sau này như% 3F và% 26. CGI.escape("http://localhost/search?q=banana&limit=7") => "http%3A%2F%2Flocalhost%2Fsearch%3Fq%3Dbanana%26limit%3D7" URI.escape("http://localhost/search?q=banana&limit=7") => "http://localhost/search?q=banana&limit=7"
Svale

2

Cách tiếp cận tốt nhất là sử dụng Hash.to_params, đây là cách tốt nhất để làm việc với mảng.

{a: 1, b: [1,2,3]}.to_param
"a=1&b[]=1&b[]=2&b[]=3"

Không có đường ray: require 'active_support/all'là cần thiết
Dorian

1

Nếu bạn đang ở trong bối cảnh của một yêu cầu Faraday, bạn cũng có thể vượt qua hàm băm params làm đối số thứ hai và faraday đảm nhiệm việc tạo một phần URL param thích hợp từ nó:

faraday_instance.get(url, params_hsh)

0

Tôi thích sử dụng đá quý này:

https://rubygems.org/gems/php_http_build_query

Sử dụng mẫu:

puts PHP.http_build_query({"a"=>"b","c"=>"d","e"=>[{"hello"=>"world","bah"=>"black"},{"hello"=>"world","bah"=>"black"}]})

# a=b&c=d&e%5B0%5D%5Bbah%5D=black&e%5B0%5D%5Bhello%5D=world&e%5B1%5D%5Bbah%5D=black&e%5B1%5D%5Bhello%5D=world

0
require 'uri'

class Hash
  def to_query_hash(key)
    reduce({}) do |h, (k, v)|
      new_key = key.nil? ? k : "#{key}[#{k}]"
      v = Hash[v.each_with_index.to_a.map(&:reverse)] if v.is_a?(Array)
      if v.is_a?(Hash)
        h.merge!(v.to_query_hash(new_key))
      else
        h[new_key] = v
      end
      h
    end
  end

  def to_query(key = nil)
    URI.encode_www_form(to_query_hash(key))
  end
end

2.4.2 :019 > {:a => "a", :b => "b"}.to_query_hash(nil)
 => {:a=>"a", :b=>"b"}

2.4.2 :020 > {:a => "a", :b => "b"}.to_query
 => "a=a&b=b"

2.4.2 :021 > {:a => "a", :b => ["c", "d", "e"]}.to_query_hash(nil)
 => {:a=>"a", "b[0]"=>"c", "b[1]"=>"d", "b[2]"=>"e"}

2.4.2 :022 > {:a => "a", :b => ["c", "d", "e"]}.to_query
 => "a=a&b%5B0%5D=c&b%5B1%5D=d&b%5B2%5D=e"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.