hyperpublic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,25 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+
9
+ gem "jeweler", "~> 1.5.1"
10
+ gem "hashie", "~>1.1.0"
11
+ gem "httparty", "~>0.7.8"
12
+ gem "rest-client", "~>1.5.1"
13
+ gem "multi_json", "~>1.0.3"
14
+ gem "json"
15
+ gem "fakeweb", "~>1.3.0"
16
+ gem "yajl-ruby"
17
+ gem 'addressable'
18
+
19
+ group :development do
20
+ gem 'ruby-debug'
21
+ gem "rspec-rails", ">= 2.2.1"
22
+ gem 'autotest-growl'
23
+ gem 'ZenTest'
24
+ #gem "rcov", ">= 0"
25
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,110 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ ZenTest (4.6.2)
5
+ actionpack (3.1.0)
6
+ activemodel (= 3.1.0)
7
+ activesupport (= 3.1.0)
8
+ builder (~> 3.0.0)
9
+ erubis (~> 2.7.0)
10
+ i18n (~> 0.6)
11
+ rack (~> 1.3.2)
12
+ rack-cache (~> 1.0.3)
13
+ rack-mount (~> 0.8.2)
14
+ rack-test (~> 0.6.1)
15
+ sprockets (~> 2.0.0)
16
+ activemodel (3.1.0)
17
+ activesupport (= 3.1.0)
18
+ bcrypt-ruby (~> 3.0.0)
19
+ builder (~> 3.0.0)
20
+ i18n (~> 0.6)
21
+ activesupport (3.1.0)
22
+ multi_json (~> 1.0)
23
+ addressable (2.2.6)
24
+ autotest-growl (0.2.11)
25
+ bcrypt-ruby (3.0.1)
26
+ builder (3.0.0)
27
+ columnize (0.3.4)
28
+ crack (0.1.8)
29
+ diff-lcs (1.1.3)
30
+ erubis (2.7.0)
31
+ fakeweb (1.3.0)
32
+ git (1.2.5)
33
+ hashie (1.1.0)
34
+ hike (1.2.1)
35
+ httparty (0.7.8)
36
+ crack (= 0.1.8)
37
+ i18n (0.6.0)
38
+ jeweler (1.5.2)
39
+ bundler (~> 1.0.0)
40
+ git (>= 1.2.5)
41
+ rake
42
+ json (1.6.0)
43
+ linecache (0.46)
44
+ rbx-require-relative (> 0.0.4)
45
+ mime-types (1.16)
46
+ multi_json (1.0.3)
47
+ rack (1.3.3)
48
+ rack-cache (1.0.3)
49
+ rack (>= 0.4)
50
+ rack-mount (0.8.3)
51
+ rack (>= 1.0.0)
52
+ rack-ssl (1.3.2)
53
+ rack
54
+ rack-test (0.6.1)
55
+ rack (>= 1.0)
56
+ railties (3.1.0)
57
+ actionpack (= 3.1.0)
58
+ activesupport (= 3.1.0)
59
+ rack-ssl (~> 1.3.2)
60
+ rake (>= 0.8.7)
61
+ rdoc (~> 3.4)
62
+ thor (~> 0.14.6)
63
+ rake (0.9.2)
64
+ rbx-require-relative (0.0.5)
65
+ rdoc (3.9.4)
66
+ rest-client (1.5.1)
67
+ mime-types (>= 1.16)
68
+ rspec (2.6.0)
69
+ rspec-core (~> 2.6.0)
70
+ rspec-expectations (~> 2.6.0)
71
+ rspec-mocks (~> 2.6.0)
72
+ rspec-core (2.6.4)
73
+ rspec-expectations (2.6.0)
74
+ diff-lcs (~> 1.1.2)
75
+ rspec-mocks (2.6.0)
76
+ rspec-rails (2.6.1)
77
+ actionpack (~> 3.0)
78
+ activesupport (~> 3.0)
79
+ railties (~> 3.0)
80
+ rspec (~> 2.6.0)
81
+ ruby-debug (0.10.4)
82
+ columnize (>= 0.1)
83
+ ruby-debug-base (~> 0.10.4.0)
84
+ ruby-debug-base (0.10.4)
85
+ linecache (>= 0.3)
86
+ sprockets (2.0.0)
87
+ hike (~> 1.2)
88
+ rack (~> 1.0)
89
+ tilt (!= 1.3.0, ~> 1.1)
90
+ thor (0.14.6)
91
+ tilt (1.3.3)
92
+ yajl-ruby (1.0.0)
93
+
94
+ PLATFORMS
95
+ ruby
96
+
97
+ DEPENDENCIES
98
+ ZenTest
99
+ addressable
100
+ autotest-growl
101
+ fakeweb (~> 1.3.0)
102
+ hashie (~> 1.1.0)
103
+ httparty (~> 0.7.8)
104
+ jeweler (~> 1.5.1)
105
+ json
106
+ multi_json (~> 1.0.3)
107
+ rest-client (~> 1.5.1)
108
+ rspec-rails (>= 2.2.1)
109
+ ruby-debug
110
+ yajl-ruby
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 etang
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ #The Hyperpublic Ruby Gem
2
+ =========================
3
+
4
+ A Ruby wrapper for the Hyperpublic REST API
5
+
6
+
7
+ Installation
8
+ -------------
9
+ gem install hyperpublic_ruby
10
+
11
+
12
+ Sample Usage
13
+ -------------
14
+ require 'hyperpublic'
15
+
16
+ auth = Hyperpublic::OAuth.new("your_key", "your_secret")
17
+
18
+
19
+ ###Working with Places
20
+ places_client = Hyperpublic::Places.new(auth)
21
+
22
+ # find a single place by ID
23
+ place = places_client.find("4dd53bffe2f2d70816000001")
24
+
25
+ # find places by a query
26
+ places = places_client.find(:q => "chicken")
27
+
28
+ # find places by a location
29
+ places = places_client.find(:location => "416 w 13th st, New York")
30
+
31
+ # find places by multiple criteria
32
+ places = places_client.find(:category => "food", "postal_code" => 10012)
33
+
34
+ # create a place
35
+ places_client.create({:display_name => "Hyperpublic HQ",
36
+ :tags => ["place_tag1", "place_tag2"].join(","),
37
+ :image_url => "http://s3.amazonaws.com/prestigedevelopment/beta/image_photos/4dd535cab47dfd026c000002/square.png?1296938636",
38
+ :phone_number => "2124857375",
39
+ :website => "www.hyperpublic.com.com",
40
+ :category_id => "4e274d89bd0286830f000170",
41
+ :address => "416 w 13th st, New York, NY 10012",
42
+ :lat => 40.7405,
43
+ :lon => -74.007})
44
+
45
+
46
+ ###Working with Geodeals & Events
47
+ offers_client = Hyperpublic::Offers.new(auth)
48
+
49
+ # find a single offer by ID
50
+ offer = offers_client.show("4e5e66f9a7ecee0001027a7b")
51
+
52
+ # find offers by a query
53
+ offers = offers_client.find(:q => "bowling")
54
+
55
+ # find offers by a location
56
+ offers = offers_client.find(:lat => 40.7, :lon => 74.0)
57
+
58
+ # find offers by multiple criteria
59
+ offers = offers_client.find(:source => "buywithme", :price_min => 10, :limit => 5)
60
+
61
+
62
+ ###Working with Categories
63
+ categories_client = Hyperpublic::Categories.new(auth)
64
+
65
+ # get a list of categories
66
+ categories = categories_client.find
67
+
68
+
69
+ Documentation
70
+ -------------
71
+ Visit our [developer site](http://developer.hyperpublic.com) for full documentation.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'lib/hyperpublic/version.rb'
2
+ require 'rubygems'
3
+ require 'bundler'
4
+
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.version = Hyperpublic::VERSION
18
+ gem.files = FileList['lib/**/*.rb', '[A-Z]*', 'spec/**/*'].to_a
19
+ gem.name = "hyperpublic_ruby"
20
+ gem.homepage = "http://github.com/jumppost/hyperpublic_api_ruby"
21
+ gem.license = "MIT"
22
+ gem.summary = %Q{Client library for the hyperpublic api}
23
+ gem.description = %Q{Provides easy access to the hyperpublic api}
24
+ gem.email = "etang@hyperpublic.com"
25
+ gem.authors = ["etang"]
26
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
27
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
28
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
29
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
30
+ end
31
+ Jeweler::RubygemsDotOrgTasks.new
32
+
33
+ require 'rspec/core'
34
+ require 'rspec/core/rake_task'
35
+ RSpec::Core::RakeTask.new(:spec) do |spec|
36
+ spec.pattern = FileList['spec/**/*_spec.rb']
37
+ end
38
+
39
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
40
+ spec.pattern = 'spec/**/*_spec.rb'
41
+ spec.rcov = true
42
+ end
43
+
44
+ task :default => :spec
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "hyperpublic #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
@@ -0,0 +1,18 @@
1
+ module Hyperpublic
2
+ class All < Base
3
+ extend Forwardable
4
+ def_delegators :client, :get, :post, :post_multi, :put, :delete
5
+
6
+ attr_reader :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def find(params)
13
+ q = Addressable::URI.new
14
+ q.query_values = stringify(params)
15
+ perform_get("/all?#{q.query}")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,117 @@
1
+ require 'cgi'
2
+
3
+ module Hyperpublic
4
+ class Base
5
+ extend Forwardable
6
+
7
+ def_delegators :client, :get, :post, :post_multi, :put, :delete
8
+
9
+ attr_reader :client
10
+
11
+ def initialize(client)
12
+ @client = client
13
+ end
14
+
15
+ def search_neighborhoods(search_string, limit)
16
+ perform_get("/locations/search_neighborhoods", {:search_string => search_string, :limit => limit})
17
+ end
18
+
19
+ def search_tags(search_string, limit)
20
+ perform_get("/tags/search", {:search_string => search_string, :limit => limit})
21
+ end
22
+
23
+ def photos(params)
24
+ query = {}
25
+ case
26
+ when params[:ids] then query[:ids] = params[:ids]
27
+ when params[:object_id] && params[:object_type] then
28
+ begin
29
+ query[:object_id] = params[:object_id]
30
+ query[:object_type] = params[:object_type]
31
+ end
32
+ end
33
+
34
+ perform_get("/photos", query)
35
+ end
36
+
37
+ protected
38
+
39
+ def self.mime_type(file)
40
+ case
41
+ when file =~ /\.jpg/ then 'image/jpg'
42
+ when file =~ /\.gif$/ then 'image/gif'
43
+ when file =~ /\.png$/ then 'image/png'
44
+ else 'application/octet-stream'
45
+ end
46
+ end
47
+
48
+ def mime_type(f) self.class.mime_type(f) end
49
+
50
+ CRLF = "\r\n"
51
+
52
+ def self.build_multipart_bodies(parts)
53
+ boundary = Time.now.to_i.to_s(16)
54
+ body = ""
55
+ parts.each do |key, value|
56
+ esc_key = CGI.escape(key.to_s)
57
+ body << "--#{boundary}#{CRLF}"
58
+ if value.respond_to?(:read)
59
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"; filename=\"#{File.basename(value.path)}\"#{CRLF}"
60
+ body << "Content-Type: #{mime_type(value.path)}#{CRLF*2}"
61
+ body << value.read
62
+ else
63
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"#{CRLF*2}#{value}"
64
+ end
65
+ body << CRLF
66
+ end
67
+ body << "--#{boundary}--#{CRLF*2}"
68
+ {
69
+ :body => body,
70
+ :headers => {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
71
+ }
72
+ end
73
+
74
+ def build_multipart_bodies(parts) self.class.build_multipart_bodies(parts) end
75
+
76
+
77
+ private
78
+
79
+ def stringify(obj)
80
+ if obj.is_a? Hash
81
+ obj.each do |key, value|
82
+ obj.delete(key)
83
+ obj[key.to_s] = stringify(value)
84
+ end
85
+ elsif obj.is_a? Array
86
+ obj.collect {|e| stringify(e)}
87
+ else
88
+ obj.to_s if obj
89
+ end
90
+ end
91
+
92
+ def arr_str(tags)
93
+ arr_str = (tags.is_a? Array) ? tags.join(",") : tags
94
+ arr_str
95
+ end
96
+
97
+ def perform_get(path, options={})
98
+ Hyperpublic::Request.get(self, path, options)
99
+ end
100
+
101
+ def perform_post(path, options={})
102
+ Hyperpublic::Request.post(self, path, options)
103
+ end
104
+
105
+ def perform_post_multi(path, options={})
106
+ Hyperpublic::Request.post_multi(self, path, options)
107
+ end
108
+
109
+ def perform_put(path, options={})
110
+ Hyperpublic::Request.put(self, path, options)
111
+ end
112
+
113
+ def perform_delete(path, options={})
114
+ Hyperpublic::Request.delete(self, path, options)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,19 @@
1
+ module Hyperpublic
2
+ class Categories< Base
3
+ extend Forwardable
4
+ def_delegators :client, :get, :post, :post_multi, :put, :delete
5
+
6
+ attr_reader :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def find(params={})
13
+ q = Addressable::URI.new
14
+ q.query_values = stringify(params)
15
+ perform_get("/categories#{("?" + q.query) unless q.query.empty?}")
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,63 @@
1
+ require 'addressable/uri'
2
+ require 'oauth'
3
+ require 'oauth/consumer'
4
+ require 'json'
5
+
6
+ module Hyperpublic
7
+ class OAuth
8
+ extend Forwardable
9
+
10
+ attr_reader :ctoken, :csecret
11
+
12
+ def initialize(ctoken, csecret, options={})
13
+ @ctoken, @csecret, @consumer_options = ctoken, csecret, {}
14
+ @api_endpoint = options[:api_endpoint] || 'https://api.hyperpublic.com/api/v1'
15
+ @consumer = ::OAuth::Consumer.new(ctoken, csecret, :site => @api_endpiont)
16
+ @token = ::OAuth::AccessToken.new(@consumer)
17
+ @signing_endpoint = options[:signing_endpoint] || 'https://api.hyperpublic.com/api/v1'
18
+ if options[:sign_in]
19
+ @consumer_options[:authorize_path] = '/oauth/authenticate'
20
+ end
21
+ @auth_params = "client_id=#{@ctoken}&client_secret=#{@csecret}" + (@token.token.empty? ? "" : "&access_token=#{@token.token}")
22
+ @auth_params_hash = {"client_id" => @ctoken, "client_secret" => @csecret}
23
+ @auth_params_hash["access_token"] = @token.token unless @token.token.empty?
24
+ end
25
+
26
+ def get(uri, options={})
27
+ path = @api_endpoint + uri
28
+ sep = (path =~ /\/.*\?/) ? "&" : "?"
29
+ @token.get(path + sep + @auth_params)
30
+ end
31
+
32
+ def post(uri, body, options={})
33
+ body.merge!("client_id" => @ctoken, "client_secret" => @csecret)
34
+ @token.post(@api_endpoint + uri, body.to_json, {"Content-Type" => 'application/json'})
35
+ end
36
+
37
+ def put(uri, body, options={})
38
+ @token.put(@api_endpoint + uri, body.merge(@auth_params_hash).to_json, {'Content-Type' => 'application/json'})
39
+ end
40
+
41
+ def delete(uri, options={})
42
+ path = @api_endpoint + uri
43
+ sep = (path =~ /\/.*\?/) ? "&" : "?"
44
+ @token.delete(path + sep + @auth_params)
45
+ end
46
+
47
+ def access_token
48
+ @access_token ||= ::OAuth::AccessToken.new(signing_consumer, @atoken, @asecret)
49
+ end
50
+
51
+ def authorize_from_access(atoken, asecret)
52
+ @atoken, @asecret = atoken, asecret
53
+ end
54
+
55
+ private
56
+
57
+ def clear_request_token
58
+ @request_token = nil
59
+ end
60
+
61
+ end
62
+ end
63
+
@@ -0,0 +1,27 @@
1
+ module Hyperpublic
2
+ class Offers < Base
3
+ extend Forwardable
4
+
5
+ def_delegators :client, :get, :post, :post_multi, :put, :delete
6
+
7
+ attr_reader :client
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ def find(params={})
14
+ if params.is_a? String
15
+ perform_get("/offers/#{params}")
16
+ else
17
+ q = Addressable::URI.new
18
+ q.query_values = stringify(params)
19
+ perform_get("/offers?#{q.query}")
20
+ end
21
+ end
22
+
23
+ def show(id)
24
+ perform_get("/offers/#{id}")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ module Hyperpublic
2
+ class People < Base
3
+ extend Forwardable
4
+ def_delegators :client, :get, :post, :post_multi, :put, :delete
5
+
6
+ attr_reader :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ #param can be
13
+ # id
14
+ # {:ids => [1, 2, 3]},
15
+ # {:tags => [tag1, tags]}, or
16
+ # {:location => {:lat=>35,:lon=>-70}}
17
+ # etc
18
+ def find(params)
19
+ if params.is_a? String
20
+ perform_get("/people/#{params}")
21
+ else
22
+ q = Addressable::URI.new
23
+ q.query_values = stringify(params)
24
+ perform_get("/people?#{q.query}")
25
+ end
26
+ end
27
+
28
+ def create(options={})
29
+ perform_post("/people", :body => options)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ module Hyperpublic
2
+ class Places < Base
3
+ extend Forwardable
4
+ def_delegators :client, :get, :post, :post_multi, :put, :delete
5
+
6
+ attr_reader :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def find(params)
13
+ if params.is_a? String
14
+ perform_get("/places/#{params}")
15
+ else
16
+ q = Addressable::URI.new
17
+ q.query_values = stringify(params)
18
+ perform_get("/places?#{q.query}")
19
+ end
20
+ end
21
+
22
+ def create(options={})
23
+ perform_post("/places", :body => options)
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,105 @@
1
+ module Hyperpublic
2
+ class Request
3
+ extend Forwardable
4
+
5
+ def self.get(client, path, options={})
6
+ new(client, :get, path, options).perform
7
+ end
8
+
9
+ def self.post(client, path, options={})
10
+ new(client, :post, path, options).perform
11
+ end
12
+
13
+ def self.post_multi(client, path, options={})
14
+ new(client, :post_multi, path, options).perform
15
+ end
16
+
17
+ def self.put(client, path, options={})
18
+ new(client, :put, path, options).perform
19
+ end
20
+
21
+ def self.delete(client, path, options={})
22
+ new(client, :delete, path, options).perform
23
+ end
24
+
25
+ attr_reader :client, :method, :path, :options
26
+
27
+ def_delegators :client, :get, :post, :post_multi, :put, :delete
28
+
29
+ def initialize(client, method, path, options={})
30
+ @client, @method, @path, @options = client, method, path, options
31
+ end
32
+
33
+ def uri
34
+ @uri ||= begin
35
+ uri = URI.parse(path)
36
+
37
+ if options[:query] && options[:query] != {}
38
+ uri.query = to_query(options[:query])
39
+ end
40
+
41
+ uri.to_s
42
+ end
43
+ end
44
+
45
+ def perform
46
+ Hyperpublic.make_friendly(send("perform_#{method}"))
47
+ end
48
+
49
+ private
50
+ def perform_get
51
+ get(uri, options[:headers])
52
+ end
53
+
54
+ def perform_post
55
+ post(uri, options[:body], options[:headers])
56
+ end
57
+
58
+ def perform_put
59
+ put(uri, options[:body], options[:headers])
60
+ end
61
+
62
+ def perform_delete
63
+ delete(uri, options[:headers])
64
+ end
65
+
66
+ def to_query(options)
67
+ options.inject([]) do |collection, opt|
68
+ collection << "#{opt[0]}=#{opt[1]}"
69
+ collection
70
+ end * '&'
71
+ end
72
+
73
+ =begin
74
+ def perform_get
75
+ #get(uri, options || {})
76
+ get(uri)
77
+ end
78
+
79
+ def perform_post
80
+ #post(uri, options[:body], options[:headers] || {})
81
+ post(uri, options[:body])
82
+ end
83
+
84
+ def perform_post_multi
85
+ post_multi(uri, options[:body], options[:headers] || {})
86
+ end
87
+
88
+ def perform_put
89
+ put(uri, options[:body], options[:headers] || {})
90
+ end
91
+
92
+ def perform_delete
93
+ delete(uri, options[:headers] || {})
94
+ end
95
+
96
+ def to_query(options)
97
+ options.inject([]) do |collection, opt|
98
+ collection << "#{opt[0]}=#{opt[1]}"
99
+ collection
100
+ end * '&'
101
+ end
102
+ =end
103
+ end
104
+ end
105
+