koala 1.0.0 → 1.2.0

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 (54) hide show
  1. data/.autotest +12 -0
  2. data/.gitignore +3 -1
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +62 -2
  5. data/Gemfile +8 -0
  6. data/Rakefile +0 -1
  7. data/autotest/discover.rb +1 -0
  8. data/koala.gemspec +13 -14
  9. data/lib/koala/batch_operation.rb +74 -0
  10. data/lib/koala/graph_api.rb +145 -132
  11. data/lib/koala/graph_batch_api.rb +97 -0
  12. data/lib/koala/graph_collection.rb +59 -0
  13. data/lib/koala/http_service.rb +176 -0
  14. data/lib/koala/oauth.rb +191 -0
  15. data/lib/koala/realtime_updates.rb +23 -29
  16. data/lib/koala/rest_api.rb +13 -8
  17. data/lib/koala/test_users.rb +33 -17
  18. data/lib/koala/uploadable_io.rb +153 -87
  19. data/lib/koala/utils.rb +11 -0
  20. data/lib/koala/version.rb +3 -0
  21. data/lib/koala.rb +59 -217
  22. data/readme.md +92 -53
  23. data/spec/cases/{api_base_spec.rb → api_spec.rb} +31 -6
  24. data/spec/cases/error_spec.rb +32 -0
  25. data/spec/cases/graph_and_rest_api_spec.rb +12 -21
  26. data/spec/cases/graph_api_batch_spec.rb +582 -0
  27. data/spec/cases/graph_api_spec.rb +11 -14
  28. data/spec/cases/graph_collection_spec.rb +116 -0
  29. data/spec/cases/http_service_spec.rb +446 -0
  30. data/spec/cases/koala_spec.rb +54 -0
  31. data/spec/cases/oauth_spec.rb +319 -213
  32. data/spec/cases/realtime_updates_spec.rb +45 -31
  33. data/spec/cases/rest_api_spec.rb +23 -7
  34. data/spec/cases/test_users_spec.rb +123 -75
  35. data/spec/cases/uploadable_io_spec.rb +120 -37
  36. data/spec/cases/utils_spec.rb +10 -0
  37. data/spec/fixtures/cat.m4v +0 -0
  38. data/spec/fixtures/facebook_data.yml +26 -24
  39. data/spec/fixtures/mock_facebook_responses.yml +203 -78
  40. data/spec/spec_helper.rb +30 -5
  41. data/spec/support/graph_api_shared_examples.rb +149 -118
  42. data/spec/support/json_testing_fix.rb +42 -0
  43. data/spec/support/koala_test.rb +187 -0
  44. data/spec/support/mock_http_service.rb +62 -58
  45. data/spec/support/ordered_hash.rb +205 -0
  46. data/spec/support/rest_api_shared_examples.rb +139 -15
  47. data/spec/support/uploadable_io_shared_examples.rb +2 -8
  48. metadata +90 -114
  49. data/lib/koala/http_services.rb +0 -146
  50. data/spec/cases/http_services/http_service_spec.rb +0 -54
  51. data/spec/cases/http_services/net_http_service_spec.rb +0 -350
  52. data/spec/cases/http_services/typhoeus_service_spec.rb +0 -144
  53. data/spec/support/live_testing_data_helper.rb +0 -40
  54. data/spec/support/setup_mocks_or_live.rb +0 -52
data/lib/koala.rb CHANGED
@@ -1,22 +1,32 @@
1
1
  require 'cgi'
2
2
  require 'digest/md5'
3
3
 
4
- require 'json'
4
+ require 'multi_json'
5
5
 
6
6
  # OpenSSL and Base64 are required to support signed_request
7
7
  require 'openssl'
8
8
  require 'base64'
9
9
 
10
10
  # include koala modules
11
- require 'koala/http_services'
11
+ require 'koala/oauth'
12
12
  require 'koala/graph_api'
13
+ require 'koala/graph_batch_api'
14
+ require 'koala/batch_operation'
15
+ require 'koala/graph_collection'
13
16
  require 'koala/rest_api'
14
17
  require 'koala/realtime_updates'
15
18
  require 'koala/test_users'
16
19
 
20
+ # HTTP module so we can communicate with Facebook
21
+ require 'koala/http_service'
22
+
17
23
  # add KoalaIO class
18
24
  require 'koala/uploadable_io'
19
25
 
26
+ # miscellaneous
27
+ require 'koala/utils'
28
+ require 'koala/version'
29
+
20
30
  module Koala
21
31
 
22
32
  module Facebook
@@ -25,6 +35,7 @@ module Koala
25
35
  # Contributors: Alex Koppel, Chris Baclig, Rafi Jacoby, and the team at Context Optional
26
36
  # http://github.com/arsduo/koala
27
37
 
38
+ # APIs
28
39
  class API
29
40
  # initialize with an access token
30
41
  def initialize(access_token = nil)
@@ -32,10 +43,13 @@ module Koala
32
43
  end
33
44
  attr_reader :access_token
34
45
 
46
+ include GraphAPIMethods
47
+ include RestAPIMethods
48
+
35
49
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
36
50
  # Fetches the given path in the Graph API.
37
51
  args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
38
-
52
+
39
53
  # add a leading /
40
54
  path = "/#{path}" unless path =~ /^\//
41
55
 
@@ -47,45 +61,42 @@ module Koala
47
61
  # in the case of a server error
48
62
  raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
49
63
 
50
- # Parse the body as JSON and check for errors if provided a mechanism to do so
64
+ # parse the body as JSON and run it through the error checker (if provided)
51
65
  # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
52
- # and cause JSON.parse to fail -- so we account for that by wrapping the result in []
53
- body = response = JSON.parse("[#{result.body.to_s}]")[0]
54
- if error_checking_block
55
- yield(body)
56
- end
66
+ # and cause MultiJson.decode to fail -- so we account for that by wrapping the result in []
67
+ body = MultiJson.decode("[#{result.body.to_s}]")[0]
68
+ yield body if error_checking_block
57
69
 
58
- # now return the desired information
59
- if options[:http_component]
60
- result.send(options[:http_component])
61
- else
62
- body
63
- end
70
+ # if we want a component other than the body (e.g. redirect header for images), return that
71
+ options[:http_component] ? result.send(options[:http_component]) : body
64
72
  end
65
73
  end
66
74
 
67
- class GraphAPI < API
68
- include GraphAPIMethods
75
+ # special enhanced APIs
76
+ class GraphBatchAPI < API
77
+ include GraphBatchAPIMethods
69
78
  end
70
79
 
71
- class RestAPI < API
72
- include RestAPIMethods
80
+ class RealtimeUpdates
81
+ include RealtimeUpdateMethods
73
82
  end
74
83
 
75
- class GraphAndRestAPI < API
76
- include GraphAPIMethods
77
- include RestAPIMethods
84
+ class TestUsers
85
+ include TestUserMethods
78
86
  end
79
87
 
80
- class RealtimeUpdates < API
81
- include RealtimeUpdateMethods
88
+ # legacy support for old APIs
89
+ class OldAPI < API;
90
+ def initialize(*args)
91
+ Koala::Utils.deprecate("#{self.class.name} is deprecated and will be removed in a future version; please use the API class instead.")
92
+ super
93
+ end
82
94
  end
95
+ class GraphAPI < OldAPI; end
96
+ class RestAPI < OldAPI; end
97
+ class GraphAndRestAPI < OldAPI; end
83
98
 
84
- class TestUsers < API
85
- include TestUserMethods
86
- # make the Graph API accessible in case someone wants to make other calls to interact with their users
87
- attr_reader :graph_api
88
- end
99
+ # Errors
89
100
 
90
101
  class APIError < StandardError
91
102
  attr_accessor :fb_error_type
@@ -94,201 +105,32 @@ module Koala
94
105
  super("#{fb_error_type}: #{details["message"]}")
95
106
  end
96
107
  end
108
+ end
97
109
 
110
+ class KoalaError < StandardError; end
98
111
 
99
- class OAuth
100
- attr_reader :app_id, :app_secret, :oauth_callback_url
101
- def initialize(app_id, app_secret, oauth_callback_url = nil)
102
- @app_id = app_id
103
- @app_secret = app_secret
104
- @oauth_callback_url = oauth_callback_url
105
- end
106
-
107
- def get_user_info_from_cookie(cookie_hash)
108
- # Parses the cookie set by the official Facebook JavaScript SDK.
109
- #
110
- # cookies should be a Hash, like the one Rails provides
111
- #
112
- # If the user is logged in via Facebook, we return a dictionary with the
113
- # keys "uid" and "access_token". The former is the user's Facebook ID,
114
- # and the latter can be used to make authenticated requests to the Graph API.
115
- # If the user is not logged in, we return None.
116
- #
117
- # Download the official Facebook JavaScript SDK at
118
- # http://github.com/facebook/connect-js/. Read more about Facebook
119
- # authentication at http://developers.facebook.com/docs/authentication/.
120
-
121
- if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
122
- # remove the opening/closing quote
123
- fb_cookie = fb_cookie.gsub(/\"/, "")
124
-
125
- # since we no longer get individual cookies, we have to separate out the components ourselves
126
- components = {}
127
- fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
128
-
129
- # generate the signature and make sure it matches what we expect
130
- auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
131
- sig = Digest::MD5.hexdigest(auth_string + @app_secret)
132
- sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
133
- end
134
- end
135
- alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
136
-
137
- def get_user_from_cookie(cookies)
138
- if info = get_user_info_from_cookies(cookies)
139
- string = info["uid"]
140
- end
141
- end
142
- alias_method :get_user_from_cookies, :get_user_from_cookie
143
-
144
- # URLs
145
-
146
- def url_for_oauth_code(options = {})
147
- # for permissions, see http://developers.facebook.com/docs/authentication/permissions
148
- permissions = options[:permissions]
149
- scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
150
- display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
151
-
152
- callback = options[:callback] || @oauth_callback_url
153
- raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
154
-
155
- # Creates the URL for oauth authorization for a given callback and optional set of permissions
156
- "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}#{display}"
157
- end
158
-
159
- def url_for_access_token(code, options = {})
160
- # Creates the URL for the token corresponding to a given code generated by Facebook
161
- callback = options[:callback] || @oauth_callback_url
162
- raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
163
- "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
164
- end
165
-
166
- def get_access_token_info(code, options = {})
167
- # convenience method to get a parsed token from Facebook for a given code
168
- # should this require an OAuth callback URL?
169
- get_token_from_server({:code => code, :redirect_uri => @oauth_callback_url}, false, options)
170
- end
171
-
172
- def get_access_token(code, options = {})
173
- # upstream methods will throw errors if needed
174
- if info = get_access_token_info(code, options)
175
- string = info["access_token"]
176
- end
177
- end
178
-
179
- def get_app_access_token_info(options = {})
180
- # convenience method to get a the application's sessionless access token
181
- get_token_from_server({:type => 'client_cred'}, true, options)
182
- end
183
-
184
- def get_app_access_token(options = {})
185
- if info = get_app_access_token_info(options)
186
- string = info["access_token"]
187
- end
188
- end
189
-
190
- # Originally provided directly by Facebook, however this has changed
191
- # as their concept of crypto changed. For historic purposes, this is their proposal:
192
- # https://developers.facebook.com/docs/authentication/canvas/encryption_proposal/
193
- # Currently see https://github.com/facebook/php-sdk/blob/master/src/facebook.php#L758
194
- # for a more accurate reference implementation strategy.
195
- def parse_signed_request(input)
196
- encoded_sig, encoded_envelope = input.split('.', 2)
197
- signature = base64_url_decode(encoded_sig).unpack("H*").first
198
- envelope = JSON.parse(base64_url_decode(encoded_envelope))
199
-
200
- raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
201
-
202
- # now see if the signature is valid (digest, key, data)
203
- hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope.tr("-_", "+/"))
204
- raise 'SignedRequest: Invalid signature' if (signature != hmac)
205
-
206
- return envelope
207
- end
208
-
209
- # from session keys
210
- def get_token_info_from_session_keys(sessions, options = {})
211
- # fetch the OAuth tokens from Facebook
212
- response = fetch_token_string({
213
- :type => 'client_cred',
214
- :sessions => sessions.join(",")
215
- }, true, "exchange_sessions", options)
216
-
217
- # Facebook returns an empty body in certain error conditions
218
- if response == ""
219
- raise APIError.new({
220
- "type" => "ArgumentError",
221
- "message" => "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!"
222
- })
223
- end
224
-
225
- JSON.parse(response)
226
- end
227
-
228
- def get_tokens_from_session_keys(sessions, options = {})
229
- # get the original hash results
230
- results = get_token_info_from_session_keys(sessions, options)
231
- # now recollect them as just the access tokens
232
- results.collect { |r| r ? r["access_token"] : nil }
233
- end
234
-
235
- def get_token_from_session_key(session, options = {})
236
- # convenience method for a single key
237
- # gets the overlaoded strings automatically
238
- get_tokens_from_session_keys([session], options)[0]
239
- end
240
-
241
- protected
242
-
243
- def get_token_from_server(args, post = false, options = {})
244
- # fetch the result from Facebook's servers
245
- result = fetch_token_string(args, post, "access_token", options)
246
-
247
- # if we have an error, parse the error JSON and raise an error
248
- raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
249
-
250
- # otherwise, parse the access token
251
- parse_access_token(result)
252
- end
253
-
254
- def parse_access_token(response_text)
255
- components = response_text.split("&").inject({}) do |hash, bit|
256
- key, value = bit.split("=")
257
- hash.merge!(key => value)
258
- end
259
- components
260
- end
261
-
262
- def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
263
- Koala.make_request("/oauth/#{endpoint}", {
264
- :client_id => @app_id,
265
- :client_secret => @app_secret
266
- }.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options)).body
267
- end
268
112
 
269
- # base 64
270
- # directly from https://github.com/facebook/crypto-request-examples/raw/master/sample.rb
271
- def base64_url_decode(str)
272
- str += '=' * (4 - str.length.modulo(4))
273
- Base64.decode64(str.tr('-_', '+/'))
274
- end
275
- end
113
+ # finally, the few things defined on the Koala module itself
114
+ class << self
115
+ attr_accessor :http_service
276
116
  end
277
117
 
278
- class KoalaError< StandardError; end
279
-
280
- # finally, set up the http service Koala methods used to make requests
281
- # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
282
118
  def self.http_service=(service)
283
- self.send(:include, service)
119
+ if service.respond_to?(:deprecated_interface)
120
+ # if this is a deprecated module, support the old interface
121
+ # by changing the default adapter so the right library is used
122
+ # we continue to use the single HTTPService module for everything
123
+ service.deprecated_interface
124
+ else
125
+ # if it's a real http_service, use it
126
+ @http_service = service
127
+ end
284
128
  end
285
129
 
286
- # by default, try requiring Typhoeus -- if that works, use it
287
- # if you have Typheous and don't want to use it (or want another service),
288
- # you can run Koala.http_service = NetHTTPService (or MyHTTPService)
289
- begin
290
- Koala.http_service = TyphoeusService
291
- rescue LoadError
292
- Koala.http_service = NetHTTPService
130
+ def self.make_request(path, args, verb, options = {})
131
+ http_service.make_request(path, args, verb, options)
293
132
  end
133
+
134
+ # we use Faraday as our main service, with mock as the other main one
135
+ self.http_service = HTTPService
294
136
  end
data/readme.md CHANGED
@@ -1,67 +1,95 @@
1
+ [![Build Status](https://secure.travis-ci.org/arsduo/koala.png)](http://travis-ci.org/arsduo/koala)
2
+
1
3
  Koala
2
4
  ====
3
- Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.com/arsduo/koala</a>) is a new Facebook library for Ruby, supporting the Graph API (including photo uploads), the old REST API, realtime updates, and OAuth validation. We wrote Koala with four goals:
5
+ [Koala](http://github.com/arsduo/koala) is a Facebook library for Ruby, supporting the Graph API (including the batch requests and photo uploads), the REST API, realtime updates, test users, and OAuth validation. We wrote Koala with four goals:
4
6
 
5
- * Lightweight: Koala should be as light and simple as Facebook’s own new libraries, providing API accessors and returning simple JSON. (We clock in, with comments, just over 750 lines of code.)
6
- * Fast: Koala should, out of the box, be quick. In addition to supporting the vanilla Ruby networking libraries, it natively supports Typhoeus, our preferred gem for making fast HTTP requests. Of course, That brings us to our next topic:
7
- * Flexible: Koala should be useful to everyone, regardless of their current configuration. (We have no dependencies beyond the JSON gem. Koala also has a built-in mechanism for using whichever HTTP library you prefer to make requests against the graph.)
8
- * Tested: Koala should have complete test coverage, so you can rely on it. (Our complete test coverage can be run against either mocked responses or the live Facebook servers.)
7
+ * Lightweight: Koala should be as light and simple as Facebook’s own libraries, providing API accessors and returning simple JSON.
8
+ * Fast: Koala should, out of the box, be quick. Out of the box, we use Facebook's faster read-only servers when possible and if available, the Typhoeus gem to make snappy Facebook requests. Of course, that brings us to our next topic:
9
+ * Flexible: Koala should be useful to everyone, regardless of their current configuration. (We support JRuby, Rubinius, and REE as well as vanilla Ruby, and use the Faraday library to provide complete flexibility over how HTTP requests are made.)
10
+ * Tested: Koala should have complete test coverage, so you can rely on it. (Our test coverage is complete and can be run against either mocked responses or the live Facebook servers.)
9
11
 
10
- 1.0
12
+ Facebook Changes on October 1, 2011
11
13
  ---
12
- Version 1.0 is due out on May 1st, 2011 with a ton of great features.
13
-
14
- sudo gem install koala
15
14
 
16
- Until then, you can install the release candidate like so:
17
-
18
- sudo gem install koala --pre
19
-
15
+ **Koala 1.2 supports all of Facebook's new authentication schemes**, which will be introduced on October 1, 2011; the old Javascript library and older authentication schemes will be deprecated at the same time.
16
+
17
+ To test your application, upgrade to the latest version of Koala (see below) and configure your application according to Facebook's [OAuth 2.0 and HTTPS Migration](https://developers.facebook.com/docs/oauth2-https-migration/) guide. If you have the appropriate calls to get_user_info_from_cookies (apps using the Javascript SDK) and/or parse_signed_params (for Canvas and tab apps), your application should work without a hitch.
18
+
19
+ _Note_: in their new secure cookie format, Facebook provides an OAuth code, which Koala automatically exchanges for an access token. Because this involves a call to Facebook's servers, you should consider storing the user's access token in their session and only calling get_user_info_from_cookies when necessary (access_token not present, you discover it's expired, etc.). Otherwise, you'll be calling out to Facebook each time the user loads a page, slowing down your site. (As we figure out best practices for this, we'll update the wiki.)
20
+
21
+ Installation
22
+ ---
23
+
24
+ Easy:
25
+
26
+ [sudo|rvm] gem install koala
27
+
28
+ Or in Bundler:
29
+
30
+ gem "koala"
31
+
20
32
  Graph API
21
33
  ----
22
34
  The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
35
+
36
+ @graph = Koala::Facebook::API.new(oauth_access_token)
37
+ # in 1.1 or earlier, use GraphAPI instead of API
38
+
39
+ profile = @graph.get_object("me")
40
+ friends = @graph.get_connections("me", "friends")
41
+ @graph.put_object("me", "feed", :message => "I am writing on my wall!")
23
42
 
24
- graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
25
- profile = graph.get_object("me")
26
- friends = graph.get_connections("me", "friends")
27
- graph.put_object("me", "feed", :message => "I am writing on my wall!")
43
+ # you can even use the new Timeline API
44
+ # see https://developers.facebook.com/docs/beta/opengraph/tutorial/
45
+ @graph.put_connections("me", "namespace:action", :object => object_url)
28
46
 
29
47
  The response of most requests is the JSON data returned from the Facebook servers as a Hash.
30
48
 
31
- When retrieving data that returns an array of results (for example, when calling GraphAPI#get_connections or GraphAPI#search) a GraphCollection object (a sub-class of Array) will be returned, which contains added methods for getting the next and previous page of results:
49
+ When retrieving data that returns an array of results (for example, when calling API#get_connections or API#search) a GraphCollection object will be returned, which makes it easy to page through the results:
32
50
 
33
51
  # Returns the feed items for the currently logged-in user as a GraphCollection
34
- feed = graph.get_connections("me", "feed")
35
-
36
- # GraphCollection is a sub-class of Array, so you can use it as a usual Array
37
- first_entry = feed[0]
38
- last_entry = feed.last
39
-
40
- # Returns the next page of results (also as a GraphCollection)
52
+ feed = @graph.get_connections("me", "feed")
53
+ feed.each {|f| do_something_with_item(f) } # it's a subclass of Array
41
54
  next_feed = feed.next_page
42
55
 
43
- # Returns an array describing the URL for the next page: [path, arguments]
44
- # This is useful for paging across multiple requests
45
- next_path, next_args = feed.next_page_params
56
+ # You can also get an array describing the URL for the next page: [path, arguments]
57
+ # This is useful for storing page state across multiple browser requests
58
+ next_page_params = feed.next_page_params
59
+ page = @graph.get_page(next_page_params)
60
+
61
+ You can also make multiple calls at once using Facebook's batch API:
46
62
 
47
- # You can use those params to easily get the next (or prevous) page
48
- page = graph.get_page(feed.next_page_params)
63
+ # Returns an array of results as if they were called non-batch
64
+ @graph.batch do |batch_api|
65
+ batch_api.get_object('me')
66
+ batch_api.put_wall_post('Making a post in a batch.')
67
+ end
49
68
 
50
- Check out the wiki for more examples.
69
+ Check out the wiki for more details and examples.
51
70
 
52
- The old-school REST API
71
+ The REST API
53
72
  -----
54
73
  Where the Graph API and the old REST API overlap, you should choose the Graph API. Unfortunately, that overlap is far from complete, and there are many important API calls that can't yet be done via the Graph.
55
74
 
56
- Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
75
+ Fortunately, Koala supports the REST API using the very same interface; to use this, instantiate an API:
57
76
 
58
- @rest = Koala::Facebook::RestAPI.new(oauth_access_token)
59
- @rest.fql_query(my_fql_query) # convenience method
60
- @rest.rest_call("stream.publish", arguments_hash) # generic version
77
+ @rest = Koala::Facebook::API.new(oauth_access_token)
78
+ # in 1.1 or earlier, use RestAPI instead of API
79
+
80
+ @rest.fql_query(my_fql_query) # convenience method
81
+ @rest.fql_multiquery(fql_query_hash) # convenience method
82
+ @rest.rest_call("stream.publish", arguments_hash) # generic version
61
83
 
62
- We reserve the right to expand the built-in REST API coverage to additional convenience methods in the future, depending on how fast Facebook moves to fill in the gaps.
84
+ Of course, you can use the Graph API methods on the same object -- the power of two APIs right in the palm of your hand.
63
85
 
64
- (If you want the power of both APIs in the palm of your hand, try out the GraphAndRestAPI class.)
86
+ @api = Koala::Facebook::API.new(oauth_access_token)
87
+ # in 1.1 or earlier, use GraphAndRestAPI instead of API
88
+
89
+ @api = Koala::Facebook::API.new(oauth_access_token)
90
+ fql = @api.fql_query(my_fql_query)
91
+ @api.put_wall_post(process_result(fql))
92
+
65
93
 
66
94
  OAuth
67
95
  -----
@@ -70,7 +98,7 @@ You can use the Graph and REST APIs without an OAuth access token, but the real
70
98
 
71
99
  If your application uses Koala and the Facebook [JavaScript SDK](http://github.com/facebook/connect-js) (formerly Facebook Connect), you can use the OAuth class to parse the cookies:
72
100
  @oauth.get_user_from_cookies(cookies) # gets the user's ID
73
- @oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
101
+ @oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
74
102
 
75
103
  And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
76
104
  # generate authenticating URL
@@ -81,20 +109,18 @@ And if you have to use the more complicated [redirect-based OAuth process](http:
81
109
  You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
82
110
  @oauth.get_app_access_token
83
111
 
84
- That's it! It's pretty simple once you get the hang of it. If you're new to OAuth, though, check out the wiki and the OAuth Playground example site (see below).
112
+ For those building apps on Facebook, parsing signed requests is simple:
113
+ @oauth.parse_signed_request(signed_request_string)
85
114
 
86
- *Signed Requests:* Excited to try out the new signed request authentication scheme? Good news! Koala now supports parsing those parameters:
87
- @oauth.parse_signed_request(request)
88
-
89
- *Exchanging session keys:* Stuck building tab applications on Facebook? Wishing you had an OAuth token so you could use the Graph API? You're in luck! Koala now allows you to exchange session keys for OAuth access tokens:
115
+ Or, if for some horrible reason, you're still using session keys, despair not! It's easy to turn them into shiny, modern OAuth tokens:
90
116
  @oauth.get_token_from_session_key(session_key)
91
117
  @oauth.get_tokens_from_session_keys(array_of_session_keys)
92
118
 
119
+ That's it! It's pretty simple once you get the hang of it. If you're new to OAuth, though, check out the wiki and the OAuth Playground example site (see below).
120
+
93
121
  Real-time Updates
94
122
  -----
95
- The Graph API now allows your application to subscribe to real-time updates for certain objects in the graph.
96
-
97
- Currently, Facebook only supports subscribing to users, permissions and errors. On top of that, there are limitations on what attributes and connections for each of these objects you can subscribe to updates for. Check the [official Facebook documentation](http://developers.facebook.com/docs/api/realtime) for more details.
123
+ Sometimes, reaching out to Facebook is a pain -- let it reach out to you instead. The Graph API allows your application to subscribe to real-time updates for certain objects in the graph; check the [official Facebook documentation](http://developers.facebook.com/docs/api/realtime) for more details on what objects you can subscribe to and what limitations may apply.
98
124
 
99
125
  Koala makes it easy to interact with your applications using the RealtimeUpdates class:
100
126
 
@@ -118,6 +144,17 @@ And to top it all off, RealtimeUpdates provides a static method to respond to Fa
118
144
 
119
145
  For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
120
146
 
147
+ Test Users
148
+ -----
149
+
150
+ We also support the test users API, allowing you to conjure up fake users and command them to do your bidding using the Graph or REST API:
151
+
152
+ @test_users = Koala::Facebook::TestUsers.new(:app_id => id, :secret => secret)
153
+ user = @test_users.create(is_app_installed, desired_permissions)
154
+ user_graph_api = Koala::Facebook::API.new(user["access_token"])
155
+ # or, if you want to make a whole community:
156
+ @test_users.create_network(network_size, is_app_installed, common_permissions)
157
+
121
158
  See examples, ask questions
122
159
  -----
123
160
  Some resources to help you as you play with Koala and the Graph API:
@@ -130,14 +167,16 @@ Testing
130
167
  -----
131
168
 
132
169
  Unit tests are provided for all of Koala's methods. By default, these tests run against mock responses and hence are ready out of the box:
133
-
170
+
134
171
  # From anywhere in the project directory:
135
- rake spec
136
-
172
+ bundle exec rake spec
173
+
137
174
 
138
175
  You can also run live tests against Facebook's servers:
139
-
176
+
140
177
  # Again from anywhere in the project directory:
141
- LIVE=true rake spec
178
+ LIVE=true bundle exec rake spec
179
+ # you can also test against Facebook's beta tier
180
+ LIVE=true BETA=true bundle exec rake spec
142
181
 
143
- Important Note: to run the live tests, you have to provide some of your own data in spec/fixtures/facebook_data.yml: a valid OAuth access token with publish\_stream, read\_stream, and user\_photos permissions and an OAuth code that can be used to generate an access token. You can get thisdata at the OAuth Playground; if you want to use your own app, remember to swap out the app ID, secret, and other values. (The file also provides valid values for other tests, which you're welcome to swap out for data specific to your own application.)
182
+ By default, the live tests are run against test users, so you can run them as frequently as you want. If you want to run them against a real user, however, you can fill in the OAuth token, code, and access\_token values in spec/fixtures/facebook_data.yml. See the wiki for more details.
@@ -44,7 +44,7 @@ describe "Koala::Facebook::API" do
44
44
 
45
45
  Koala.stub(:make_request).and_return(response)
46
46
 
47
- @service.api('anything', 'get', {}, :http_component => http_component)
47
+ @service.api('anything', {}, 'get', :http_component => http_component)
48
48
  end
49
49
 
50
50
  it "should return the body of the request as JSON if no http_component is given" do
@@ -52,21 +52,21 @@ describe "Koala::Facebook::API" do
52
52
  Koala.stub(:make_request).and_return(response)
53
53
 
54
54
  json_body = mock('JSON body')
55
- JSON.stub(:parse).and_return([json_body])
55
+ MultiJson.stub(:decode).and_return([json_body])
56
56
 
57
57
  @service.api('anything').should == json_body
58
58
  end
59
59
 
60
- it "should execute a block with the response body if passed one" do
60
+ it "should execute an error checking block if provided" do
61
61
  body = '{}'
62
62
  Koala.stub(:make_request).and_return(Koala::Response.new(200, body, {}))
63
63
 
64
64
  yield_test = mock('Yield Tester')
65
65
  yield_test.should_receive(:pass)
66
66
 
67
- @service.api('anything') do |arg|
67
+ @service.api('anything', {}, "get") do |arg|
68
68
  yield_test.pass
69
- arg.should == JSON.parse(body)
69
+ arg.should == MultiJson.decode(body)
70
70
  end
71
71
  end
72
72
 
@@ -98,4 +98,29 @@ describe "Koala::Facebook::API" do
98
98
  end
99
99
  end
100
100
 
101
- end
101
+ describe "with an access token" do
102
+ before(:each) do
103
+ @api = Koala::Facebook::API.new(@token)
104
+ end
105
+
106
+ it_should_behave_like "Koala RestAPI"
107
+ it_should_behave_like "Koala RestAPI with an access token"
108
+
109
+ it_should_behave_like "Koala GraphAPI"
110
+ it_should_behave_like "Koala GraphAPI with an access token"
111
+ it_should_behave_like "Koala GraphAPI with GraphCollection"
112
+ end
113
+
114
+ describe "without an access token" do
115
+ before(:each) do
116
+ @api = Koala::Facebook::API.new
117
+ end
118
+
119
+ it_should_behave_like "Koala RestAPI"
120
+ it_should_behave_like "Koala RestAPI without an access token"
121
+
122
+ it_should_behave_like "Koala GraphAPI"
123
+ it_should_behave_like "Koala GraphAPI without an access token"
124
+ it_should_behave_like "Koala GraphAPI with GraphCollection"
125
+ end
126
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Koala::Facebook::APIError do
4
+ it "is a StandardError" do
5
+ Koala::Facebook::APIError.new.should be_a(StandardError)
6
+ end
7
+
8
+ it "has an accessor for fb_error_type" do
9
+ Koala::Facebook::APIError.instance_methods.map(&:to_sym).should include(:fb_error_type)
10
+ Koala::Facebook::APIError.instance_methods.map(&:to_sym).should include(:fb_error_type=)
11
+ end
12
+
13
+ it "sets fb_error_type to details['type']" do
14
+ type = "foo"
15
+ Koala::Facebook::APIError.new("type" => type).fb_error_type.should == type
16
+ end
17
+
18
+ it "sets the error message details['type']: details['message']" do
19
+ type = "foo"
20
+ message = "bar"
21
+ error = Koala::Facebook::APIError.new("type" => type, "message" => message)
22
+ error.message.should =~ /#{type}/
23
+ error.message.should =~ /#{message}/
24
+ end
25
+ end
26
+
27
+ describe Koala::KoalaError do
28
+ it "is a StandardError" do
29
+ Koala::KoalaError.new.should be_a(StandardError)
30
+ end
31
+ end
32
+