koala 1.3.0rc1 → 1.3.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.
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