koala 1.0.0 → 1.1.0rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.autotest ADDED
@@ -0,0 +1,12 @@
1
+ # Override autotest default magic to rerun all tests every time a
2
+ # change is detected on the file system.
3
+ class Autotest
4
+
5
+ def get_to_green
6
+ begin
7
+ rerun_all_tests
8
+ wait_for_changes unless all_good
9
+ end until all_good
10
+ end
11
+
12
+ end
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  pkg
2
2
  .project
3
- Gemfile.lock
3
+ Gemfile.lock
4
+ .rvmrc
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ v1.1
2
+ New methods:
3
+ -- Batch API support through Koala::Facebook::GraphAPI.batch (thanks, seejohnrun!)
4
+ -- includes file uploads, error handling, and FQL
5
+ -- Added GraphAPI#get_comments_for_urls (thanks, amrnt!)
6
+ -- Added RestAPI#fql_multiquery, which simplifies the results (thanks, amrnt!)
7
+ Updated methods:
8
+ -- RealtimeUpdates now uses a GraphAPI object instead of its own API
9
+ -- RestAPI#rest_call now has an optional last argument for method, for calls requiring POST, DELETE, etc. (thanks, sshilo!)
10
+ -- Filename can now be specified when uploading (e.g. for Ads API) (thanks, sshilo!)
11
+ -- get_objects([]) returns [] instead of a Facebook error in non-batch mode (thanks, aselder!)
12
+ Internal improvements:
13
+ -- HTTP services are more modular and can be changed on the fly (thanks, chadk!)
14
+ -- Includes support for uploading StringIOs and other non-files via Net::HTTP even when using TyphoeusService
15
+ -- Support for global proxy and timeout settings (thanks, itchy!)
16
+ -- Support for setting certificate path and file to address Net::HTTP errors under Ruby 1.9.2
17
+ -- Koala now uses the modern Typhoeus API (thanks, aselder!)
18
+
1
19
  v1.0
2
20
  New methods:
3
21
  -- Photo and file upload now supported through #put_picture
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
data/koala.gemspec CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{koala}
5
- s.version = "1.0.0"
6
- s.date = %q{2011-05-01}
5
+ s.version = "1.1.0rc2"
6
+ s.date = %q{2011-06-06}
7
7
 
8
8
  s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the REST API, realtime updates, and OAuth authentication.}
9
9
  s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph and REST APIs, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
@@ -30,20 +30,20 @@ Gem::Specification.new do |s|
30
30
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
31
31
  s.add_runtime_dependency(%q<json>, ["~> 1.0"])
32
32
  s.add_runtime_dependency(%q<multipart-post>, ["~> 1.0"])
33
- s.add_development_dependency(%q<rspec>, ["~> 2.5.0"])
33
+ s.add_development_dependency(%q<rspec>, ["~> 2.5"])
34
34
  s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
35
35
  s.add_development_dependency(%q<typhoeus>, ["~> 0.2.4"])
36
36
  else
37
37
  s.add_dependency(%q<json>, ["~> 1.0"])
38
38
  s.add_dependency(%q<multipart-post>, ["~> 1.0"])
39
- s.add_dependency(%q<rspec>, ["~> 2.5.0"])
39
+ s.add_dependency(%q<rspec>, ["~> 2.5"])
40
40
  s.add_dependency(%q<rake>, ["~> 0.8.7"])
41
41
  s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
42
42
  end
43
43
  else
44
44
  s.add_dependency(%q<json>, ["~> 1.0"])
45
45
  s.add_dependency(%q<multipart-post>, ["~> 1.0"])
46
- s.add_dependency(%q<rspec>, ["~> 2.5.0"])
46
+ s.add_dependency(%q<rspec>, ["~> 2.5"])
47
47
  s.add_dependency(%q<rake>, ["~> 0.8.7"])
48
48
  s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
49
49
  end
@@ -28,7 +28,20 @@ module Koala
28
28
  # If you are using the JavaScript SDK, you can use the
29
29
  # Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
30
30
  # for the active user from the cookie saved by the SDK.
31
-
31
+
32
+ def self.included(base)
33
+ base.class_eval do
34
+ def self.check_response(response)
35
+ # check for Graph API-specific errors
36
+ # this returns an error, which is immediately raised (non-batch)
37
+ # or added to the list of batch results (batch)
38
+ if response.is_a?(Hash) && error_details = response["error"]
39
+ APIError.new(error_details)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
32
45
  # Objects
33
46
 
34
47
  def get_object(id, args = {}, options = {})
@@ -37,10 +50,10 @@ module Koala
37
50
  end
38
51
 
39
52
  def get_objects(ids, args = {}, options = {})
40
- # Fetchs all of the given object from the graph.
41
- # We return a map from ID to object. If any of the IDs are invalid,
42
- # we raise an exception.
43
- graph_call("", args.merge("ids" => ids.join(",")), "get", options)
53
+ # Fetchs all of the given objects from the graph.
54
+ # If any of the IDs are invalid, they'll raise an exception.
55
+ return [] if ids.empty?
56
+ graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
44
57
  end
45
58
 
46
59
  def put_object(parent_object, connection_name, args = {}, options = {})
@@ -71,10 +84,19 @@ module Koala
71
84
 
72
85
  def get_connections(id, connection_name, args = {}, options = {})
73
86
  # Fetchs the connections for given object.
74
- result = graph_call("#{id}/#{connection_name}", args, "get", options)
75
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
87
+ graph_call("#{id}/#{connection_name}", args, "get", options) do |result|
88
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
89
+ end
76
90
  end
77
91
 
92
+ def get_comments_for_urls(urls = [], args = {}, options = {})
93
+ # Fetchs the comments for given URLs (array or comma-separated string)
94
+ # see https://developers.facebook.com/blog/post/490
95
+ return [] if urls.empty?
96
+ args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
97
+ get_object("comments", args, options)
98
+ end
99
+
78
100
  def put_connections(id, connection_name, args = {}, options = {})
79
101
  # Posts a certain connection
80
102
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
@@ -93,8 +115,9 @@ module Koala
93
115
 
94
116
  def get_picture(object, args = {}, options = {})
95
117
  # Gets a picture object, returning the URL (which Facebook sends as a header)
96
- result = graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers))
97
- result["Location"]
118
+ graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
119
+ result["Location"]
120
+ end
98
121
  end
99
122
 
100
123
  def put_picture(*picture_args)
@@ -123,7 +146,9 @@ module Koala
123
146
  options = picture_args[3 + args_offset] || {}
124
147
 
125
148
  args["source"] = Koala::UploadableIO.new(*picture_args.slice(0, 1 + args_offset))
126
-
149
+
150
+ options[:http_service] = Koala.base_http_service if args["source"].requires_base_http_service
151
+
127
152
  self.put_object(target_id, "photos", args, options)
128
153
  end
129
154
 
@@ -172,47 +197,62 @@ module Koala
172
197
 
173
198
  def search(search_terms, args = {}, options = {})
174
199
  args.merge!({:q => search_terms}) unless search_terms.nil?
175
- result = graph_call("search", args, "get", options)
176
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
200
+ graph_call("search", args, "get", options) do |result|
201
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
202
+ end
177
203
  end
178
204
 
179
205
  # API access
180
-
181
- def graph_call(*args)
206
+
207
+ # Make a call which may or may not be batched
208
+ def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
182
209
  # Direct access to the Facebook API
183
210
  # see any of the above methods for example invocations
184
- response = api(*args) do |response|
185
- # check for Graph API-specific errors
186
- if response.is_a?(Hash) && error_details = response["error"]
187
- raise APIError.new(error_details)
211
+ unless GraphAPI.batch_mode?
212
+ result = api(path, args, verb, options) do |response|
213
+ if error = GraphAPI.check_response(response)
214
+ raise error
215
+ end
188
216
  end
217
+
218
+ # now process as appropriate (get picture header, make GraphCollection, etc.)
219
+ post_processing ? post_processing.call(result) : result
220
+ else
221
+ # for batch APIs, we queue up the call details (incl. post-processing)
222
+ GraphAPI.batch_calls << BatchOperation.new(
223
+ :url => path,
224
+ :args => args,
225
+ :method => verb,
226
+ :access_token => @access_token,
227
+ :http_options => options,
228
+ :post_processing => post_processing
229
+ )
230
+ nil # batch operations return nothing immediately
189
231
  end
190
-
191
- response
192
- end
232
+ end
193
233
 
194
234
  # GraphCollection support
195
-
196
235
  def get_page(params)
197
236
  # Pages through a set of results stored in a GraphCollection
198
237
  # Used for connections and search results
199
- result = graph_call(*params)
200
- result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
238
+ graph_call(*params) do |result|
239
+ result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
240
+ end
201
241
  end
202
242
 
203
243
  end
204
244
 
205
245
 
206
246
  class GraphCollection < Array
207
- #This class is a light wrapper for collections returned
208
- #from the Graph API.
247
+ # This class is a light wrapper for collections returned
248
+ # from the Graph API.
209
249
  #
210
- #It extends Array to allow direct access to the data colleciton
211
- #which should allow it to drop in seamlessly.
250
+ # It extends Array to allow direct access to the data colleciton
251
+ # which should allow it to drop in seamlessly.
212
252
  #
213
- #It also allows access to paging information and the
214
- #ability to get the next/previous page in the collection
215
- #by calling next_page or previous_page.
253
+ # It also allows access to paging information and the
254
+ # ability to get the next/previous page in the collection
255
+ # by calling next_page or previous_page.
216
256
  attr_reader :paging
217
257
  attr_reader :api
218
258
 
@@ -0,0 +1,151 @@
1
+ module Koala
2
+ module Facebook
3
+ class BatchOperation
4
+ attr_reader :access_token, :http_options, :post_processing, :files
5
+
6
+ def initialize(options = {})
7
+ @args = (options[:args] || {}).dup # because we modify it below
8
+ @access_token = options[:access_token]
9
+ @http_options = (options[:http_options] || {}).dup # dup because we modify it below
10
+ @batch_args = @http_options.delete(:batch_args) || {}
11
+ @url = options[:url]
12
+ @method = options[:method].to_sym
13
+ @post_processing = options[:post_processing]
14
+
15
+ process_binary_args
16
+
17
+ raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
18
+ end
19
+
20
+ def to_batch_params(main_access_token)
21
+ # set up the arguments
22
+ args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
23
+
24
+ response = {
25
+ :method => @method,
26
+ :relative_url => @url,
27
+ }
28
+
29
+ # handle batch-level arguments, such as name, depends_on, and attached_files
30
+ @batch_args[:attached_files] = @files.keys.join(",") if @files
31
+ response.merge!(@batch_args) if @batch_args
32
+
33
+ # for get and delete, we append args to the URL string
34
+ # otherwise, they go in the body
35
+ if args_string.length > 0
36
+ if args_in_url?
37
+ response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
38
+ else
39
+ response[:body] = args_string if args_string.length > 0
40
+ end
41
+ end
42
+
43
+ response
44
+ end
45
+
46
+ protected
47
+
48
+ def process_binary_args
49
+ # collect binary files
50
+ @args.each_pair do |key, value|
51
+ if UploadableIO.binary_content?(value)
52
+ @files ||= {}
53
+ # we use object_id to ensure unique file identifiers across multiple batch operations
54
+ # remove it from the original hash and add it to the file store
55
+ id = "file#{GraphAPI.batch_calls.length}_#{@files.keys.length}"
56
+ @files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
57
+ end
58
+ end
59
+ end
60
+
61
+ def args_in_url?
62
+ @method == :get || @method == :delete
63
+ end
64
+ end
65
+
66
+ module GraphAPIBatchMethods
67
+ def self.included(base)
68
+ base.class_eval do
69
+ # batch mode flags
70
+ def self.batch_mode?
71
+ !!@batch_mode
72
+ end
73
+
74
+ def self.batch_calls
75
+ raise KoalaError, "GraphAPI.batch_calls accessed when not in batch block!" unless batch_mode?
76
+ @batch_calls
77
+ end
78
+
79
+ def self.batch(http_options = {}, &block)
80
+ @batch_mode = true
81
+ @batch_http_options = http_options
82
+ @batch_calls = []
83
+ yield
84
+ begin
85
+ results = batch_api(@batch_calls)
86
+ ensure
87
+ @batch_mode = false
88
+ end
89
+ results
90
+ end
91
+
92
+ def self.batch_api(batch_calls)
93
+ return [] unless batch_calls.length > 0
94
+ # Facebook requires a top-level access token
95
+
96
+ # Get the access token for the user and start building a hash to store params
97
+ # Turn the call args collected into what facebook expects
98
+ args = {}
99
+ access_token = args["access_token"] = batch_calls.first.access_token
100
+ args['batch'] = batch_calls.map { |batch_op|
101
+ args.merge!(batch_op.files) if batch_op.files
102
+ batch_op.to_batch_params(access_token)
103
+ }.to_json
104
+
105
+ # Make the POST request for the batch call
106
+ # batch operations have to go over SSL, but since there's an access token, that secures that
107
+ result = Koala.make_request('/', args, 'post', @batch_http_options)
108
+ # Raise an error if we get a 500
109
+ raise APIError.new("type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}") if result.status >= 500
110
+
111
+ response = JSON.parse(result.body.to_s)
112
+ # raise an error if we get a Batch API error message
113
+ raise APIError.new("type" => "Error #{response["error"]}", "message" => response["error_description"]) if response.is_a?(Hash) && response["error"]
114
+
115
+ # otherwise, map the results with post-processing included
116
+ index = 0 # keep compat with ruby 1.8 - no with_index for map
117
+ response.map do |call_result|
118
+ # Get the options hash
119
+ batch_op = batch_calls[index]
120
+ index += 1
121
+
122
+ if call_result
123
+ # (see note in regular api method about JSON parsing)
124
+ body = JSON.parse("[#{call_result['body'].to_s}]")[0]
125
+ unless call_result["code"].to_i >= 500 || error = GraphAPI.check_response(body)
126
+ # Get the HTTP component they want
127
+ data = case batch_op.http_options[:http_component]
128
+ when :status
129
+ call_result["code"].to_i
130
+ when :headers
131
+ # facebook returns the headers as an array of k/v pairs, but we want a regular hash
132
+ call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
133
+ else
134
+ body
135
+ end
136
+
137
+ # process it if we are given a block to process with
138
+ batch_op.post_processing ? batch_op.post_processing.call(data) : data
139
+ else
140
+ error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
141
+ end
142
+ else
143
+ nil
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,87 @@
1
+ require "net/http" unless defined?(Net::HTTP)
2
+ require "net/https"
3
+ require "net/http/post/multipart"
4
+
5
+ module Koala
6
+ module NetHTTPService
7
+ # this service uses Net::HTTP to send requests to the graph
8
+ include Koala::HTTPService
9
+
10
+ def self.make_request(path, args, verb, options = {})
11
+ # We translate args to a valid query string. If post is specified,
12
+ # we send a POST request to the given path with the given arguments.
13
+
14
+ # by default, we use SSL only for private requests
15
+ # this makes public requests faster
16
+ private_request = args["access_token"] || @always_use_ssl || options[:use_ssl]
17
+
18
+ # if proxy/timeout options aren't passed, check if defaults are set
19
+ options[:proxy] ||= proxy
20
+ options[:timeout] ||= timeout
21
+
22
+ # if the verb isn't get or post, send it as a post argument
23
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
24
+
25
+ http = create_http(server(options), private_request, options)
26
+
27
+ result = http.start do |http|
28
+ response, body = if verb == "post"
29
+ if params_require_multipart? args
30
+ http.request Net::HTTP::Post::Multipart.new path, encode_multipart_params(args)
31
+ else
32
+ http.post(path, encode_params(args))
33
+ end
34
+ else
35
+ http.get("#{path}?#{encode_params(args)}")
36
+ end
37
+
38
+ Koala::Response.new(response.code.to_i, body, response)
39
+ end
40
+ end
41
+
42
+ protected
43
+ def self.encode_params(param_hash)
44
+ # unfortunately, we can't use to_query because that's Rails, not Ruby
45
+ # if no hash (e.g. no auth token) return empty string
46
+ ((param_hash || {}).collect do |key_and_value|
47
+ key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
48
+ "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
49
+ end).join("&")
50
+ end
51
+
52
+ def self.encode_multipart_params(param_hash)
53
+ Hash[*param_hash.collect do |key, value|
54
+ [key, value.kind_of?(Koala::UploadableIO) ? value.to_upload_io : value]
55
+ end.flatten]
56
+ end
57
+
58
+ def self.create_http(server, private_request, options)
59
+ if options[:proxy]
60
+ proxy = URI.parse(options[:proxy])
61
+ http = Net::HTTP.new(server, private_request ? 443 : nil,
62
+ proxy.host, proxy.port, proxy.user, proxy.password)
63
+ else
64
+ http = Net::HTTP.new(server, private_request ? 443 : nil)
65
+ end
66
+
67
+ if options[:timeout]
68
+ http.open_timeout = options[:timeout]
69
+ http.read_timeout = options[:timeout]
70
+ end
71
+
72
+ # For HTTPS requests, set the proper CA certificates
73
+ if private_request
74
+ http.use_ssl = true
75
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
76
+
77
+ options[:ca_file] ||= ca_file
78
+ http.ca_file = options[:ca_file] if options[:ca_file] && File.exists?(options[:ca_file])
79
+
80
+ options[:ca_path] ||= ca_path
81
+ http.ca_path = options[:ca_path] if options[:ca_path] && Dir.exists?(options[:ca_path])
82
+ end
83
+
84
+ http
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,37 @@
1
+ require "typhoeus" unless defined?(Typhoeus)
2
+
3
+ module Koala
4
+ module TyphoeusService
5
+ # this service uses Typhoeus to send requests to the graph
6
+ include Typhoeus
7
+ include Koala::HTTPService
8
+
9
+ def self.make_request(path, args, verb, options = {})
10
+ # if the verb isn't get or post, send it as a post argument
11
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
12
+
13
+ # switch any UploadableIOs to the files Typhoeus expects
14
+ args.each_pair {|key, value| args[key] = value.to_file if value.is_a?(UploadableIO)}
15
+
16
+ # you can pass arguments directly to Typhoeus using the :typhoeus_options key
17
+ typhoeus_options = {:params => args}.merge(options[:typhoeus_options] || {})
18
+
19
+ # if proxy/timeout options aren't passed, check if defaults are set
20
+ typhoeus_options[:proxy] ||= proxy
21
+ typhoeus_options[:timeout] ||= timeout
22
+
23
+ # by default, we use SSL only for private requests (e.g. with access token)
24
+ # this makes public requests faster
25
+ prefix = (args["access_token"] || @always_use_ssl || options[:use_ssl]) ? "https" : "http"
26
+
27
+ response = Typhoeus::Request.send(verb, "#{prefix}://#{server(options)}#{path}", typhoeus_options)
28
+ Koala::Response.new(response.code, response.body, response.headers_hash)
29
+ end
30
+
31
+ protected
32
+
33
+ def self.multipart_requires_content_type?
34
+ false # Typhoeus handles multipart file types, we don't have to require it
35
+ end
36
+ end
37
+ end
@@ -13,134 +13,32 @@ module Koala
13
13
  def self.included(base)
14
14
  base.class_eval do
15
15
  class << self
16
- attr_accessor :always_use_ssl
16
+ attr_accessor :always_use_ssl, :proxy, :timeout, :ca_path, :ca_file
17
17
  end
18
-
18
+
19
19
  def self.server(options = {})
20
20
  "#{options[:beta] ? "beta." : ""}#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
21
21
  end
22
-
23
- protected
24
22
 
25
- def self.params_require_multipart?(param_hash)
26
- param_hash.any? { |key, value| value.kind_of?(Koala::UploadableIO) }
27
- end
28
-
29
- def self.multipart_requires_content_type?
30
- true
31
- end
32
- end
33
- end
34
- end
35
-
36
- module NetHTTPService
37
- # this service uses Net::HTTP to send requests to the graph
38
- def self.included(base)
39
- base.class_eval do
40
- require "net/http" unless defined?(Net::HTTP)
41
- require "net/https"
42
- require "net/http/post/multipart"
43
-
44
- include Koala::HTTPService
45
-
46
- def self.make_request(path, args, verb, options = {})
47
- # We translate args to a valid query string. If post is specified,
48
- # we send a POST request to the given path with the given arguments.
49
-
50
- # by default, we use SSL only for private requests
51
- # this makes public requests faster
52
- private_request = args["access_token"] || @always_use_ssl || options[:use_ssl]
53
-
54
- # if the verb isn't get or post, send it as a post argument
55
- args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
56
-
57
- http = create_http(server(options), private_request, options)
58
- http.use_ssl = true if private_request
59
-
60
- result = http.start do |http|
61
- response, body = if verb == "post"
62
- if params_require_multipart? args
63
- http.request Net::HTTP::Post::Multipart.new path, encode_multipart_params(args)
64
- else
65
- http.post(path, encode_params(args))
66
- end
67
- else
68
- http.get("#{path}?#{encode_params(args)}")
69
- end
70
-
71
- Koala::Response.new(response.code.to_i, body, response)
72
- end
73
- end
74
-
75
- protected
76
23
  def self.encode_params(param_hash)
77
24
  # unfortunately, we can't use to_query because that's Rails, not Ruby
78
25
  # if no hash (e.g. no auth token) return empty string
79
26
  ((param_hash || {}).collect do |key_and_value|
80
- key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
27
+ key_and_value[1] = key_and_value[1].to_json unless key_and_value[1].is_a? String
81
28
  "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
82
29
  end).join("&")
83
30
  end
31
+
32
+ protected
84
33
 
85
- def self.encode_multipart_params(param_hash)
86
- Hash[*param_hash.collect do |key, value|
87
- [key, value.kind_of?(Koala::UploadableIO) ? value.to_upload_io : value]
88
- end.flatten]
89
- end
90
-
91
- def self.create_http(server, private_request, options)
92
- if options[:proxy]
93
- proxy = URI.parse(options[:proxy])
94
- http = Net::HTTP.new(server, private_request ? 443 : nil,
95
- proxy.host, proxy.port, proxy.user, proxy.password)
96
- else
97
- http = Net::HTTP.new(server, private_request ? 443 : nil)
98
- end
99
- if options[:timeout]
100
- http.open_timeout = options[:timeout]
101
- http.read_timeout = options[:timeout]
102
- end
103
- http
104
- end
105
-
106
- end
107
- end
108
- end
109
-
110
- module TyphoeusService
111
- # this service uses Typhoeus to send requests to the graph
112
-
113
- def self.included(base)
114
- base.class_eval do
115
- require "typhoeus" unless defined?(Typhoeus)
116
- include Typhoeus
117
-
118
- include Koala::HTTPService
119
-
120
- def self.make_request(path, args, verb, options = {})
121
- # if the verb isn't get or post, send it as a post argument
122
- args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
123
-
124
- # switch any UploadableIOs to the files Typhoeus expects
125
- args.each_pair {|key, value| args[key] = value.to_file if value.is_a?(UploadableIO)}
126
-
127
- # you can pass arguments directly to Typhoeus using the :typhoeus_options key
128
- typhoeus_options = {:params => args}.merge(options[:typhoeus_options] || {})
129
-
130
- # by default, we use SSL only for private requests (e.g. with access token)
131
- # this makes public requests faster
132
- prefix = (args["access_token"] || @always_use_ssl || options[:use_ssl]) ? "https" : "http"
133
-
134
- response = self.send(verb, "#{prefix}://#{server(options)}#{path}", typhoeus_options)
135
- Koala::Response.new(response.code, response.body, response.headers_hash)
34
+ def self.params_require_multipart?(param_hash)
35
+ param_hash.any? { |key, value| value.kind_of?(Koala::UploadableIO) }
136
36
  end
137
-
138
- private
139
-
37
+
140
38
  def self.multipart_requires_content_type?
141
- false # Typhoeus handles multipart file types, we don't have to require it
39
+ true
142
40
  end
143
- end # class_eval
41
+ end
144
42
  end
145
43
  end
146
44
  end