koala 1.3.0rc1 → 1.3.0rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +3 -1
  2. data/.travis.yml +4 -0
  3. data/.yardopts +3 -0
  4. data/CHANGELOG +7 -0
  5. data/Gemfile +14 -0
  6. data/Guardfile +6 -0
  7. data/lib/koala.rb +16 -97
  8. data/lib/koala/api.rb +93 -0
  9. data/lib/koala/api/batch_operation.rb +83 -0
  10. data/lib/koala/api/graph_api.rb +476 -0
  11. data/lib/koala/{graph_batch_api.rb → api/graph_batch_api.rb} +20 -16
  12. data/lib/koala/api/graph_collection.rb +107 -0
  13. data/lib/koala/api/legacy.rb +26 -0
  14. data/lib/koala/{rest_api.rb → api/rest_api.rb} +33 -3
  15. data/lib/koala/http_service.rb +69 -19
  16. data/lib/koala/http_service/multipart_request.rb +41 -0
  17. data/lib/koala/http_service/response.rb +18 -0
  18. data/lib/koala/http_service/uploadable_io.rb +187 -0
  19. data/lib/koala/oauth.rb +117 -14
  20. data/lib/koala/realtime_updates.rb +89 -51
  21. data/lib/koala/test_users.rb +109 -33
  22. data/lib/koala/utils.rb +4 -0
  23. data/lib/koala/version.rb +1 -1
  24. data/spec/cases/api_spec.rb +19 -12
  25. data/spec/cases/graph_api_batch_spec.rb +41 -41
  26. data/spec/cases/http_service_spec.rb +1 -22
  27. data/spec/cases/legacy_spec.rb +107 -0
  28. data/spec/cases/multipart_request_spec.rb +5 -5
  29. data/spec/cases/oauth_spec.rb +9 -9
  30. data/spec/cases/realtime_updates_spec.rb +154 -47
  31. data/spec/cases/test_users_spec.rb +268 -219
  32. data/spec/fixtures/mock_facebook_responses.yml +10 -6
  33. data/spec/support/graph_api_shared_examples.rb +17 -12
  34. data/spec/support/koala_test.rb +1 -1
  35. data/spec/support/mock_http_service.rb +2 -2
  36. data/spec/support/rest_api_shared_examples.rb +1 -1
  37. metadata +82 -104
  38. data/lib/koala/batch_operation.rb +0 -74
  39. data/lib/koala/graph_api.rb +0 -289
  40. data/lib/koala/graph_collection.rb +0 -63
  41. data/lib/koala/multipart_request.rb +0 -35
  42. data/lib/koala/uploadable_io.rb +0 -181
  43. data/spec/cases/graph_and_rest_api_spec.rb +0 -22
  44. data/spec/cases/graph_api_spec.rb +0 -22
  45. data/spec/cases/rest_api_spec.rb +0 -22
@@ -1,22 +1,17 @@
1
+ require 'koala/api'
2
+ require 'koala/api/batch_operation'
3
+
1
4
  module Koala
2
5
  module Facebook
3
- module GraphBatchAPIMethods
4
-
5
- def self.included(base)
6
- base.class_eval do
7
- attr_reader :original_api
8
-
9
- def initialize(access_token, api)
10
- super(access_token)
11
- @original_api = api
12
- end
13
-
14
- alias_method :graph_call_outside_batch, :graph_call
15
- alias_method :graph_call, :graph_call_in_batch
6
+ # @private
7
+ class GraphBatchAPI < API
8
+ # inside a batch call we can do anything a regular Graph API can do
9
+ include GraphAPIMethods
16
10
 
17
- alias_method :check_graph_api_response, :check_response
18
- alias_method :check_response, :check_graph_batch_api_response
19
- end
11
+ attr_reader :original_api
12
+ def initialize(access_token, api)
13
+ super(access_token)
14
+ @original_api = api
20
15
  end
21
16
 
22
17
  def batch_calls
@@ -45,6 +40,15 @@ module Koala
45
40
  end
46
41
  end
47
42
 
43
+ # redefine the graph_call and check_response methods
44
+ # so we can use this API inside the batch block just like any regular Graph API
45
+ alias_method :graph_call_outside_batch, :graph_call
46
+ alias_method :graph_call, :graph_call_in_batch
47
+
48
+ alias_method :check_graph_api_response, :check_response
49
+ alias_method :check_response, :check_graph_batch_api_response
50
+
51
+ # execute the queued batch calls
48
52
  def execute(http_options = {})
49
53
  return [] unless batch_calls.length > 0
50
54
  # Turn the call args collected into what facebook expects
@@ -0,0 +1,107 @@
1
+ module Koala
2
+ module Facebook
3
+ class API
4
+ # A light wrapper for collections returned from the Graph API.
5
+ # It extends Array to allow you to page backward and forward through
6
+ # result sets, and providing easy access to paging information.
7
+ class GraphCollection < Array
8
+
9
+ # The raw paging information from Facebook (next/previous URLs).
10
+ attr_reader :paging
11
+ # @return [Koala::Facebook::GraphAPI] the api used to make requests.
12
+ attr_reader :api
13
+ # The entire raw response from Facebook.
14
+ attr_reader :raw_response
15
+
16
+ # Initialize the array of results and store various additional paging-related information.
17
+ #
18
+ # @param response the response from Facebook (a hash whose "data" key is an array)
19
+ # @param api the Graph {Koala::Facebook::API API} instance to use to make calls
20
+ # (usually the API that made the original call).
21
+ #
22
+ # @return [Koala::Facebook::GraphCollection] an initialized GraphCollection
23
+ # whose paging, raw_response, and api attributes are populated.
24
+ def initialize(response, api)
25
+ super response["data"]
26
+ @paging = response["paging"]
27
+ @raw_response = response
28
+ @api = api
29
+ end
30
+
31
+ # @private
32
+ # Turn the response into a GraphCollection if they're pageable;
33
+ # if not, return the original response.
34
+ # The Ads API (uniquely so far) returns a hash rather than an array when queried
35
+ # with get_connections.
36
+ def self.evaluate(response, api)
37
+ response.is_a?(Hash) && response["data"].is_a?(Array) ? self.new(response, api) : response
38
+ end
39
+
40
+ # Retrieve the next page of results.
41
+ #
42
+ # @return a GraphCollection array of additional results (an empty array if there are no more results)
43
+ def next_page
44
+ base, args = next_page_params
45
+ base ? @api.get_page([base, args]) : nil
46
+ end
47
+
48
+ # Retrieve the previous page of results.
49
+ #
50
+ # @return a GraphCollection array of additional results (an empty array if there are no earlier results)
51
+ def previous_page
52
+ base, args = previous_page_params
53
+ base ? @api.get_page([base, args]) : nil
54
+ end
55
+
56
+ # Arguments that can be sent to {Koala::Facebook::API#graph_call} to retrieve the next page of results.
57
+ #
58
+ # @example
59
+ # @api.graph_call(*collection.next_page_params)
60
+ #
61
+ # @return an array of arguments, or nil if there are no more pages
62
+ def next_page_params
63
+ @paging && @paging["next"] ? parse_page_url(@paging["next"]) : nil
64
+ end
65
+
66
+ # Arguments that can be sent to {Koala::Facebook::API#graph_call} to retrieve the previous page of results.
67
+ #
68
+ # @example
69
+ # @api.graph_call(*collection.previous_page_params)
70
+ #
71
+ # @return an array of arguments, or nil if there are no previous pages
72
+ def previous_page_params
73
+ @paging && @paging["previous"] ? parse_page_url(@paging["previous"]) : nil
74
+ end
75
+
76
+ # @private
77
+ def parse_page_url(url)
78
+ GraphCollection.parse_page_url(url)
79
+ end
80
+
81
+ # Parse the previous and next page URLs Facebook provides in pageable results.
82
+ # You'll mainly need to use this when using a non-Rails framework (one without url_for);
83
+ # to store paging information between page loads, pass the URL (from GraphCollection#paging)
84
+ # and use parse_page_url to turn it into parameters useful for {Koala::Facebook::API#get_page}.
85
+ #
86
+ # @param url the paging URL to turn into graph_call parameters
87
+ #
88
+ # @return an array of parameters that can be provided via graph_call(*parsed_params)
89
+ def self.parse_page_url(url)
90
+ match = url.match(/.com\/(.*)\?(.*)/)
91
+ base = match[1]
92
+ args = match[2]
93
+ params = CGI.parse(args)
94
+ new_params = {}
95
+ params.each_pair do |key,value|
96
+ new_params[key] = value.join ","
97
+ end
98
+ [base,new_params]
99
+ end
100
+ end
101
+ end
102
+
103
+ # @private
104
+ # legacy support for when GraphCollection lived directly under Koala::Facebook
105
+ GraphCollection = API::GraphCollection
106
+ end
107
+ end
@@ -0,0 +1,26 @@
1
+ require 'koala/api'
2
+ module Koala
3
+ module Facebook
4
+ # Legacy support for old pre-1.2 APIs
5
+
6
+ # A wrapper for the old APIs deprecated in 1.2.0, which triggers a deprecation warning when used.
7
+ # Otherwise, this class functions identically to API.
8
+ # @see API
9
+ # @private
10
+ class OldAPI < API
11
+ def initialize(*args)
12
+ Koala::Utils.deprecate("#{self.class.name} is deprecated and will be removed in a future version; please use the API class instead.")
13
+ super
14
+ end
15
+ end
16
+
17
+ # @private
18
+ class GraphAPI < OldAPI; end
19
+
20
+ # @private
21
+ class RestAPI < OldAPI; end
22
+
23
+ # @private
24
+ class GraphAndRestAPI < OldAPI; end
25
+ end
26
+ end
@@ -2,16 +2,45 @@ module Koala
2
2
  module Facebook
3
3
  REST_SERVER = "api.facebook.com"
4
4
 
5
- module RestAPIMethods
5
+ # Methods used to interact with Facebook's legacy REST API.
6
+ # Where possible, you should use the newer, faster Graph API to interact with Facebook;
7
+ # in the future, the REST API will be deprecated.
8
+ # For now, though, there are a few methods that can't be done through the Graph API.
9
+ #
10
+ # When using the REST API, Koala will use Facebook's faster read-only servers
11
+ # whenever the call allows.
12
+ #
13
+ # See https://github.com/arsduo/koala/wiki/REST-API for a general introduction to Koala
14
+ # and the Rest API.
15
+ module RestAPIMethods
16
+ # Set a Facebook application's properties.
17
+ #
18
+ # @param properties a hash of properties you want to update with their new values.
19
+ # @param (see #rest_call)
20
+ # @param options (see #rest_call)
21
+ #
22
+ # @return true if successful, false if not. (This call currently doesn't give useful feedback on failure.)
6
23
  def set_app_properties(properties, args = {}, options = {})
7
24
  raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "setAppProperties requires an access token"}) unless @access_token
8
25
  rest_call("admin.setAppProperties", args.merge(:properties => MultiJson.encode(properties)), options, "post")
9
26
  end
10
27
 
11
- def rest_call(fb_method, args = {}, options = {}, method = "get")
28
+ # Make a call to the REST API.
29
+ #
30
+ # @note The order of the last two arguments is non-standard (for historical reasons). Sorry.
31
+ #
32
+ # @param fb_method the API call you want to make
33
+ # @param args (see Koala::Facebook::GraphAPIMethods#graph_call)
34
+ # @param options (see Koala::Facebook::GraphAPIMethods#graph_call)
35
+ # @param verb (see Koala::Facebook::GraphAPIMethods#graph_call)
36
+ #
37
+ # @raise [Koala::Facebook::APIError] if Facebook returns an error
38
+ #
39
+ # @return the result from Facebook
40
+ def rest_call(fb_method, args = {}, options = {}, verb = "get")
12
41
  options = options.merge!(:rest_api => true, :read_only => READ_ONLY_METHODS.include?(fb_method.to_s))
13
42
 
14
- api("method/#{fb_method}", args.merge('format' => 'json'), method, options) do |response|
43
+ api("method/#{fb_method}", args.merge('format' => 'json'), verb, options) do |response|
15
44
  # check for REST API-specific errors
16
45
  if response.is_a?(Hash) && response["error_code"]
17
46
  raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
@@ -19,6 +48,7 @@ module Koala
19
48
  end
20
49
  end
21
50
 
51
+ # @private
22
52
  # read-only methods for which we can use API-read
23
53
  # taken directly from the FB PHP library (https://github.com/facebook/php-sdk/blob/master/src/facebook.php)
24
54
  READ_ONLY_METHODS = [
@@ -1,29 +1,37 @@
1
1
  require 'faraday'
2
+ require 'koala/http_service/multipart_request'
3
+ require 'koala/http_service/uploadable_io'
4
+ require 'koala/http_service/response'
2
5
 
3
6
  module Koala
4
- class Response
5
- attr_reader :status, :body, :headers
6
- def initialize(status, body, headers)
7
- @status = status
8
- @body = body
9
- @headers = headers
10
- end
11
- end
12
-
13
- module HTTPService
7
+ module HTTPService
14
8
  class << self
15
-
16
- attr_accessor :faraday_middleware, :http_options
9
+ # A customized stack of Faraday middleware that will be used to make each request.
10
+ attr_accessor :faraday_middleware
11
+ # A default set of HTTP options (see https://github.com/arsduo/koala/wiki/HTTP-Services)
12
+ attr_accessor :http_options
17
13
  end
18
14
 
19
15
  @http_options ||= {}
20
16
 
17
+ # Koala's default middleware stack.
18
+ # We encode requests in a Facebook-compatible multipart request,
19
+ # and use whichever adapter has been configured for this application.
21
20
  DEFAULT_MIDDLEWARE = Proc.new do |builder|
22
- builder.use Koala::MultipartRequest
21
+ builder.use Koala::HTTPService::MultipartRequest
23
22
  builder.request :url_encoded
24
23
  builder.adapter Faraday.default_adapter
25
24
  end
26
25
 
26
+ # The address of the appropriate Facebook server.
27
+ #
28
+ # @param options various flags to indicate which server to use.
29
+ # @option options :rest_api use the old REST API instead of the Graph API
30
+ # @option options :video use the server designated for video uploads
31
+ # @option options :beta use the beta tier
32
+ # @option options :use_ssl force https, even if not needed
33
+ #
34
+ # @return a complete server address with protocol
27
35
  def self.server(options = {})
28
36
  server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
29
37
  server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
@@ -31,6 +39,23 @@ module Koala
31
39
  "#{options[:use_ssl] ? "https" : "http"}://#{server}"
32
40
  end
33
41
 
42
+ # Makes a request directly to Facebook.
43
+ # @note You'll rarely need to call this method directly.
44
+ #
45
+ # @see Koala::Facebook::API#api
46
+ # @see Koala::Facebook::GraphAPIMethods#graph_call
47
+ # @see Koala::Facebook::RestAPIMethods#rest_call
48
+ #
49
+ # @param path the server path for this request
50
+ # @param args (see Koala::Facebook::API#api)
51
+ # @param verb the HTTP method to use.
52
+ # If not get or post, this will be turned into a POST request with the appropriate :method
53
+ # specified in the arguments.
54
+ # @param options (see Koala::Facebook::API#api)
55
+ #
56
+ # @raise an appropriate connection error if unable to make the request to Facebook
57
+ #
58
+ # @return [Koala::HTTPService::Response] a response object representing the results from Facebook
34
59
  def self.make_request(path, args, verb, options = {})
35
60
  # if the verb isn't get or post, send it as a post argument
36
61
  args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
@@ -47,13 +72,20 @@ module Koala
47
72
  conn = Faraday.new(server(request_options), request_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
48
73
 
49
74
  response = conn.send(verb, path, (verb == "post" ? params : {}))
50
- Koala::Response.new(response.status.to_i, response.body, response.headers)
51
- end
52
-
75
+ Koala::HTTPService::Response.new(response.status.to_i, response.body, response.headers)
76
+ end
77
+
78
+ # Encodes a given hash into a query string.
79
+ # This is used mainly by the Batch API nowadays, since Faraday handles this for regular cases.
80
+ #
81
+ # @param params_hash a hash of values to CGI-encode and appropriately join
82
+ #
83
+ # @example
84
+ # Koala.http_service.encode_params({:a => 2, :b => "My String"})
85
+ # => "a=2&b=My+String"
86
+ #
87
+ # @return the appropriately-encoded string
53
88
  def self.encode_params(param_hash)
54
- # unfortunately, we can't use to_query because that's Rails, not Ruby
55
- # if no hash (e.g. no auth token) return empty string
56
- # this is used mainly by the Batch API nowadays
57
89
  ((param_hash || {}).collect do |key_and_value|
58
90
  key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
59
91
  "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
@@ -63,76 +95,92 @@ module Koala
63
95
  # deprecations
64
96
  # not elegant or compact code, but temporary
65
97
 
98
+ # @private
66
99
  def self.always_use_ssl
67
100
  Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
68
101
  http_options[:use_ssl]
69
102
  end
70
103
 
104
+ # @private
71
105
  def self.always_use_ssl=(value)
72
106
  Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
73
107
  http_options[:use_ssl] = value
74
108
  end
75
109
 
110
+ # @private
76
111
  def self.timeout
77
112
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
78
113
  http_options[:timeout]
79
114
  end
80
115
 
116
+ # @private
81
117
  def self.timeout=(value)
82
118
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
83
119
  http_options[:timeout] = value
84
120
  end
85
121
 
122
+ # @private
86
123
  def self.timeout
87
124
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
88
125
  http_options[:timeout]
89
126
  end
90
127
 
128
+ # @private
91
129
  def self.timeout=(value)
92
130
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
93
131
  http_options[:timeout] = value
94
132
  end
95
133
 
134
+ # @private
96
135
  def self.proxy
97
136
  Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
98
137
  http_options[:proxy]
99
138
  end
100
139
 
140
+ # @private
101
141
  def self.proxy=(value)
102
142
  Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
103
143
  http_options[:proxy] = value
104
144
  end
105
145
 
146
+ # @private
106
147
  def self.ca_path
107
148
  Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
108
149
  (http_options[:ssl] || {})[:ca_path]
109
150
  end
110
151
 
152
+ # @private
111
153
  def self.ca_path=(value)
112
154
  Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
113
155
  (http_options[:ssl] ||= {})[:ca_path] = value
114
156
  end
115
157
 
158
+ # @private
116
159
  def self.ca_file
117
160
  Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
118
161
  (http_options[:ssl] || {})[:ca_file]
119
162
  end
120
163
 
164
+ # @private
121
165
  def self.ca_file=(value)
122
166
  Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
123
167
  (http_options[:ssl] ||= {})[:ca_file] = value
124
168
  end
125
169
 
170
+ # @private
126
171
  def self.verify_mode
127
172
  Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
128
173
  (http_options[:ssl] || {})[:verify_mode]
129
174
  end
130
175
 
176
+ # @private
131
177
  def self.verify_mode=(value)
132
178
  Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
133
179
  (http_options[:ssl] ||= {})[:verify_mode] = value
134
180
  end
135
181
 
182
+ private
183
+
136
184
  def self.process_options(options)
137
185
  if typhoeus_options = options.delete(:typhoeus_options)
138
186
  Koala::Utils.deprecate("typhoeus_options should now be included directly in the http_options hash. Support for this key will be removed in a future version.")
@@ -158,6 +206,7 @@ module Koala
158
206
  end
159
207
  end
160
208
 
209
+ # @private
161
210
  module TyphoeusService
162
211
  def self.deprecated_interface
163
212
  # support old-style interface with a warning
@@ -166,6 +215,7 @@ module Koala
166
215
  end
167
216
  end
168
217
 
218
+ # @private
169
219
  module NetHTTPService
170
220
  def self.deprecated_interface
171
221
  # support old-style interface with a warning