koala 1.5.0 → 1.6.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.
@@ -0,0 +1,86 @@
1
+ module Koala
2
+
3
+ class KoalaError < StandardError; end
4
+
5
+ module Facebook
6
+
7
+ # The OAuth signature is incomplete, invalid, or using an unsupported algorithm
8
+ class OAuthSignatureError < ::Koala::KoalaError; end
9
+
10
+ # Facebook responded with an error to an API request. If the exception contains a nil
11
+ # http_status, then the error was detected before making a call to Facebook. (e.g. missing access token)
12
+ class APIError < ::Koala::KoalaError
13
+ attr_accessor :fb_error_type, :fb_error_code, :fb_error_subcode, :fb_error_message,
14
+ :http_status, :response_body
15
+
16
+ # Create a new API Error
17
+ #
18
+ # @param http_status [Integer] The HTTP status code of the response
19
+ # @param response_body [String] The response body
20
+ # @param error_info One of the following:
21
+ # [Hash] The error information extracted from the request
22
+ # ("type", "code", "error_subcode", "message")
23
+ # [String] The error description
24
+ # If error_info is nil or not provided, the method will attempt to extract
25
+ # the error info from the response_body
26
+ #
27
+ # @return the newly created APIError
28
+ def initialize(http_status, response_body, error_info = nil)
29
+ if response_body
30
+ self.response_body = response_body.strip
31
+ else
32
+ self.response_body = ''
33
+ end
34
+ self.http_status = http_status
35
+
36
+ if error_info && error_info.is_a?(String)
37
+ message = error_info
38
+ else
39
+ unless error_info
40
+ begin
41
+ error_info = MultiJson.load(response_body)['error'] if response_body
42
+ rescue
43
+ end
44
+ error_info ||= {}
45
+ end
46
+
47
+ self.fb_error_type = error_info["type"]
48
+ self.fb_error_code = error_info["code"]
49
+ self.fb_error_subcode = error_info["error_subcode"]
50
+ self.fb_error_message = error_info["message"]
51
+
52
+ error_array = []
53
+ %w(type code error_subcode message).each do |key|
54
+ error_array << "#{key}: #{error_info[key]}" if error_info[key]
55
+ end
56
+
57
+ if error_array.empty?
58
+ message = self.response_body
59
+ else
60
+ message = error_array.join(', ')
61
+ end
62
+ end
63
+ message += " [HTTP #{http_status}]" if http_status
64
+
65
+ super(message)
66
+ end
67
+ end
68
+
69
+ # Facebook returned an invalid response body
70
+ class BadFacebookResponse < APIError; end
71
+
72
+ # Facebook responded with an error while attempting to request an access token
73
+ class OAuthTokenRequestError < APIError; end
74
+
75
+ # Any error with a 5xx HTTP status code
76
+ class ServerError < APIError; end
77
+
78
+ # Any error with a 4xx HTTP status code
79
+ class ClientError < APIError; end
80
+
81
+ # All graph API authentication failures.
82
+ class AuthenticationError < ClientError; end
83
+
84
+ end
85
+
86
+ end
data/lib/koala/oauth.rb CHANGED
@@ -139,7 +139,7 @@ module Koala
139
139
  # @param code (see #url_for_access_token)
140
140
  # @param options any additional parameters to send to Facebook when redeeming the token
141
141
  #
142
- # @raise Koala::Facebook::APIError if Facebook returns an error response
142
+ # @raise Koala::Facebook::OAuthTokenRequestError if Facebook returns an error response
143
143
  #
144
144
  # @return a hash of the access token info returned by Facebook (token, expiration, etc.)
145
145
  def get_access_token_info(code, options = {})
@@ -221,21 +221,21 @@ module Koala
221
221
  #
222
222
  # @param input the signed request from Facebook
223
223
  #
224
- # @raise RuntimeError if the signature is incomplete, invalid, or using an unsupported algorithm
224
+ # @raise OAuthSignatureError if the signature is incomplete, invalid, or using an unsupported algorithm
225
225
  #
226
226
  # @return a hash of the validated request information
227
227
  def parse_signed_request(input)
228
228
  encoded_sig, encoded_envelope = input.split('.', 2)
229
- raise 'SignedRequest: Invalid (incomplete) signature data' unless encoded_sig && encoded_envelope
229
+ raise OAuthSignatureError, 'Invalid (incomplete) signature data' unless encoded_sig && encoded_envelope
230
230
 
231
231
  signature = base64_url_decode(encoded_sig).unpack("H*").first
232
232
  envelope = MultiJson.load(base64_url_decode(encoded_envelope))
233
233
 
234
- raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
234
+ raise OAuthSignatureError, "Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
235
235
 
236
236
  # now see if the signature is valid (digest, key, data)
237
237
  hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope)
238
- raise 'SignedRequest: Invalid signature' if (signature != hmac)
238
+ raise OAuthSignatureError, 'Invalid signature' if (signature != hmac)
239
239
 
240
240
  envelope
241
241
  end
@@ -254,10 +254,7 @@ module Koala
254
254
 
255
255
  # Facebook returns an empty body in certain error conditions
256
256
  if response == ""
257
- raise APIError.new({
258
- "type" => "ArgumentError",
259
- "message" => "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!"
260
- })
257
+ raise BadFacebookResponse.new(200, '', "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!")
261
258
  end
262
259
 
263
260
  MultiJson.load(response)
@@ -282,13 +279,8 @@ module Koala
282
279
 
283
280
  def get_token_from_server(args, post = false, options = {})
284
281
  # fetch the result from Facebook's servers
285
- result = fetch_token_string(args, post, "access_token", options)
286
-
287
- # if we have an error, parse the error JSON and raise an error
288
- raise APIError.new((MultiJson.load(result)["error"] rescue nil) || {}) if result =~ /error/
289
-
290
- # otherwise, parse the access token
291
- parse_access_token(result)
282
+ response = fetch_token_string(args, post, "access_token", options)
283
+ parse_access_token(response)
292
284
  end
293
285
 
294
286
  def parse_access_token(response_text)
@@ -318,9 +310,12 @@ module Koala
318
310
  if code = components["code"]
319
311
  begin
320
312
  token_info = get_access_token_info(code, :redirect_uri => '')
321
- rescue Koala::Facebook::APIError => err
322
- return nil if err.message =~ /Code was invalid or expired/
323
- raise
313
+ rescue Koala::Facebook::OAuthTokenRequestError => err
314
+ if err.fb_error_type == 'OAuthException' && err.fb_error_message =~ /Code was invalid or expired/
315
+ return nil
316
+ else
317
+ raise
318
+ end
324
319
  end
325
320
 
326
321
  components.merge(token_info) if token_info
@@ -331,10 +326,15 @@ module Koala
331
326
  end
332
327
 
333
328
  def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
334
- Koala.make_request("/oauth/#{endpoint}", {
329
+ response = Koala.make_request("/oauth/#{endpoint}", {
335
330
  :client_id => @app_id,
336
331
  :client_secret => @app_secret
337
- }.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options)).body
332
+ }.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options))
333
+
334
+ raise ServerError.new(response.status, response.body) if response.status >= 500
335
+ raise OAuthTokenRequestError.new(response.status, response.body) if response.status >= 400
336
+
337
+ response.body
338
338
  end
339
339
 
340
340
  # base 64
@@ -7,19 +7,19 @@ module Koala
7
7
  # @note: to subscribe to real-time updates, you must have an application access token
8
8
  # or provide the app secret when initializing your RealtimeUpdates object.
9
9
 
10
- # The application API interface used to communicate with Facebook.
11
- # @return [Koala::Facebook::API]
10
+ # The application API interface used to communicate with Facebook.
11
+ # @return [Koala::Facebook::API]
12
12
  attr_reader :api
13
13
  attr_reader :app_id, :app_access_token, :secret
14
14
 
15
- # Create a new RealtimeUpdates instance.
16
- # If you don't have your app's access token, provide the app's secret and
15
+ # Create a new RealtimeUpdates instance.
16
+ # If you don't have your app's access token, provide the app's secret and
17
17
  # Koala will make a request to Facebook for the appropriate token.
18
- #
18
+ #
19
19
  # @param options initialization options.
20
20
  # @option options :app_id the application's ID.
21
21
  # @option options :app_access_token an application access token, if known.
22
- # @option options :secret the application's secret.
22
+ # @option options :secret the application's secret.
23
23
  #
24
24
  # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
25
25
  def initialize(options = {})
@@ -39,7 +39,7 @@ module Koala
39
39
  @api = API.new(@app_access_token)
40
40
  end
41
41
 
42
- # Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
42
+ # Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
43
43
  # See {http://developers.facebook.com/docs/reference/api/realtime the realtime updates documentation}
44
44
  # for more information on what objects and fields you can register for.
45
45
  #
@@ -48,11 +48,11 @@ module Koala
48
48
  # @param object a Facebook ID (name or number)
49
49
  # @param fields the fields you want your app to be updated about
50
50
  # @param callback_url the URL Facebook should ping when an update is available
51
- # @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
51
+ # @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
52
52
  # (see the docs for more information)
53
53
  # @param options (see Koala::HTTPService.make_request)
54
54
  #
55
- # @return true if successful, false (or an APIError) otherwise.
55
+ # @raise A subclass of Koala::Facebook::APIError if the subscription request failed.
56
56
  def subscribe(object, fields, callback_url, verify_token, options = {})
57
57
  args = {
58
58
  :object => object,
@@ -60,23 +60,23 @@ module Koala
60
60
  :callback_url => callback_url,
61
61
  }.merge(verify_token ? {:verify_token => verify_token} : {})
62
62
  # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
63
- @api.graph_call(subscription_path, args, 'post', options.merge(:http_component => :status)) == 200
63
+ @api.graph_call(subscription_path, args, 'post', options)
64
64
  end
65
65
 
66
- # Unsubscribe from updates for a particular object or from updates.
66
+ # Unsubscribe from updates for a particular object or from updates.
67
67
  #
68
- # @param object the object whose subscriptions to delete.
68
+ # @param object the object whose subscriptions to delete.
69
69
  # If no object is provided, all subscriptions will be removed.
70
70
  # @param options (see Koala::HTTPService.make_request)
71
71
  #
72
- # @return true if the unsubscription is successful, false (or an APIError) otherwise.
72
+ # @raise A subclass of Koala::Facebook::APIError if the subscription request failed.
73
73
  def unsubscribe(object = nil, options = {})
74
- @api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options.merge(:http_component => :status)) == 200
74
+ @api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options)
75
75
  end
76
76
 
77
77
  # List all active subscriptions for this application.
78
- #
79
- # @param options (see Koala::HTTPService.make_request)
78
+ #
79
+ # @param options (see Koala::HTTPService.make_request)
80
80
  #
81
81
  # @return [Array] a list of active subscriptions
82
82
  def list_subscriptions(options = {})
@@ -89,12 +89,12 @@ module Koala
89
89
  #
90
90
  # @param params the request parameters sent by Facebook. (You can pass in a Rails params hash.)
91
91
  # @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one
92
- #
92
+ #
93
93
  # @yield verify_token if you need to compute the verification token
94
94
  # (for instance, if your callback URL includes a record ID, which you look up
95
- # and use to calculate a hash), you can pass meet_challenge a block, which
95
+ # and use to calculate a hash), you can pass meet_challenge a block, which
96
96
  # will receive the verify_token received back from Facebook.
97
- #
97
+ #
98
98
  # @return the challenge string to be sent back to Facebook, or false if the request is invalid.
99
99
  def self.meet_challenge(params, verify_token = nil, &verification_block)
100
100
  if params["hub.mode"] == "subscribe" &&
@@ -111,12 +111,33 @@ module Koala
111
111
  false
112
112
  end
113
113
  end
114
-
114
+
115
+ # Public: As a security measure, all updates from facebook are signed using
116
+ # X-Hub-Signature: sha1=XXXX where XXX is the sha1 of the json payload
117
+ # using your application secret as the key.
118
+ #
119
+ # Example:
120
+ # # in Rails controller
121
+ # # @oauth being a previously defined Koala::Facebook::OAuth instance
122
+ # def receive_update
123
+ # if @oauth.validate_update(request.body, headers)
124
+ # ...
125
+ # end
126
+ # end
127
+ def validate_update(body, headers)
128
+ if request_signature = headers['X-Hub-Signature'] || headers['HTTP_X_HUB_SIGNATURE'] and
129
+ signature_parts = request_signature.split("sha1=")
130
+ request_signature = signature_parts[1]
131
+ calculated_signature = OpenSSL::HMAC.hexdigest('sha1', @secret, body)
132
+ calculated_signature == request_signature
133
+ end
134
+ end
135
+
115
136
  # The Facebook subscription management URL for your application.
116
137
  def subscription_path
117
138
  @subscription_path ||= "#{@app_id}/subscriptions"
118
139
  end
119
-
140
+
120
141
  # @private
121
142
  def graph_api
122
143
  Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
data/lib/koala/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Koala
2
- VERSION = "1.5.0"
2
+ VERSION = "1.6.0"
3
3
  end
data/lib/koala.rb CHANGED
@@ -3,6 +3,7 @@ require 'digest/md5'
3
3
  require 'multi_json'
4
4
 
5
5
  # include koala modules
6
+ require 'koala/errors'
6
7
  require 'koala/api'
7
8
  require 'koala/oauth'
8
9
  require 'koala/realtime_updates'
@@ -20,8 +21,6 @@ module Koala
20
21
  # See http://github.com/arsduo/koala/wiki for a general introduction to Koala
21
22
  # and the Graph API.
22
23
 
23
- class KoalaError < StandardError; end
24
-
25
24
  # Making HTTP requests
26
25
  class << self
27
26
  # Control which HTTP service framework Koala uses.
data/readme.md CHANGED
@@ -13,52 +13,78 @@ Installation
13
13
  ---
14
14
 
15
15
  Easy:
16
-
17
- [sudo|rvm] gem install koala
16
+ ```bash
17
+ [sudo|rvm] gem install koala
18
+ ```
18
19
 
19
20
  Or in Bundler:
20
-
21
- gem "koala"
21
+ ```ruby
22
+ gem "koala"
23
+ ```
22
24
 
23
25
  Graph API
24
26
  ----
25
- The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
27
+ The Graph API is the simple, slick new interface to Facebook's data.
28
+ Using it with Koala is quite straightforward. First, you'll need an access token, which you can get through
29
+ Facebook's [Graph API Explorer](https://developers.facebook.com/tools/explorer) (click on 'Get Access Token').
30
+ Then, go exploring:
26
31
 
27
- @graph = Koala::Facebook::API.new(oauth_access_token)
28
- # in 1.1 or earlier, use GraphAPI instead of API
32
+ ```ruby
33
+ @graph = Koala::Facebook::API.new(oauth_access_token)
29
34
 
30
- profile = @graph.get_object("me")
31
- friends = @graph.get_connections("me", "friends")
32
- @graph.put_connections("me", "feed", :message => "I am writing on my wall!")
35
+ profile = @graph.get_object("me")
36
+ friends = @graph.get_connections("me", "friends")
37
+ @graph.put_connections("me", "feed", :message => "I am writing on my wall!")
33
38
 
34
- # three-part queries are easy too!
35
- @graph.get_connections("me", "mutualfriends/#{friend_id}")
39
+ # three-part queries are easy too!
40
+ @graph.get_connections("me", "mutualfriends/#{friend_id}")
36
41
 
37
- # you can even use the new Timeline API
38
- # see https://developers.facebook.com/docs/beta/opengraph/tutorial/
39
- @graph.put_connections("me", "namespace:action", :object => object_url)
42
+ # you can even use the new Timeline API
43
+ # see https://developers.facebook.com/docs/beta/opengraph/tutorial/
44
+ @graph.put_connections("me", "namespace:action", :object => object_url)
45
+ ```
40
46
 
41
47
  The response of most requests is the JSON data returned from the Facebook servers as a Hash.
42
48
 
43
- 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:
49
+ When retrieving data that returns an array of results (for example, when calling `API#get_connections` or `API#search`)
50
+ a GraphCollection object will be returned, which makes it easy to page through the results:
44
51
 
45
- # Returns the feed items for the currently logged-in user as a GraphCollection
46
- feed = @graph.get_connections("me", "feed")
47
- feed.each {|f| do_something_with_item(f) } # it's a subclass of Array
48
- next_feed = feed.next_page
52
+ ```ruby
53
+ # Returns the feed items for the currently logged-in user as a GraphCollection
54
+ feed = @graph.get_connections("me", "feed")
55
+ feed.each {|f| do_something_with_item(f) } # it's a subclass of Array
56
+ next_feed = feed.next_page
49
57
 
50
- # You can also get an array describing the URL for the next page: [path, arguments]
51
- # This is useful for storing page state across multiple browser requests
52
- next_page_params = feed.next_page_params
53
- page = @graph.get_page(next_page_params)
58
+ # You can also get an array describing the URL for the next page: [path, arguments]
59
+ # This is useful for storing page state across multiple browser requests
60
+ next_page_params = feed.next_page_params
61
+ page = @graph.get_page(next_page_params)
62
+ ```
54
63
 
55
64
  You can also make multiple calls at once using Facebook's batch API:
65
+ ```ruby
66
+ # Returns an array of results as if they were called non-batch
67
+ @graph.batch do |batch_api|
68
+ batch_api.get_object('me')
69
+ batch_api.put_wall_post('Making a post in a batch.')
70
+ end
71
+ ```
72
+
73
+ You can pass a "post-processing" block to each of Koala's Graph API methods. This is handy for two reasons:
74
+
75
+ 1. You can modify the result returned by the Graph API method:
76
+
77
+ education = @graph.get_object("me") { |data| data['education'] }
78
+ # returned value only contains the "education" portion of the profile
56
79
 
57
- # Returns an array of results as if they were called non-batch
58
- @graph.batch do |batch_api|
59
- batch_api.get_object('me')
60
- batch_api.put_wall_post('Making a post in a batch.')
61
- end
80
+ 2. You can consume the data in place which is particularly useful in the batch case, so you don't have to pull
81
+ the results apart from a long list of array entries:
82
+
83
+ @graph.batch do |batch_api|
84
+ # Assuming you have database fields "about_me" and "photos"
85
+ batch_api.get_object('me') {|me| self.about_me = me }
86
+ batch_api.get_connections('me', 'photos') {|photos| self.photos = photos }
87
+ end
62
88
 
63
89
  Check out the wiki for more details and examples.
64
90
 
@@ -67,55 +93,56 @@ The REST API
67
93
  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.
68
94
 
69
95
  Fortunately, Koala supports the REST API using the very same interface; to use this, instantiate an API:
96
+ ```ruby
97
+ @rest = Koala::Facebook::API.new(oauth_access_token)
70
98
 
71
- @rest = Koala::Facebook::API.new(oauth_access_token)
72
- # in 1.1 or earlier, use RestAPI instead of API
73
-
74
- @rest.fql_query(my_fql_query) # convenience method
75
- @rest.fql_multiquery(fql_query_hash) # convenience method
76
- @rest.rest_call("stream.publish", arguments_hash) # generic version
99
+ @rest.fql_query(my_fql_query) # convenience method
100
+ @rest.fql_multiquery(fql_query_hash) # convenience method
101
+ @rest.rest_call("stream.publish", arguments_hash) # generic version
102
+ ```
77
103
 
78
104
  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.
105
+ ```ruby
106
+ @api = Koala::Facebook::API.new(oauth_access_token)
79
107
 
80
- @api = Koala::Facebook::API.new(oauth_access_token)
81
- # in 1.1 or earlier, use GraphAndRestAPI instead of API
82
-
83
- @api = Koala::Facebook::API.new(oauth_access_token)
84
- fql = @api.fql_query(my_fql_query)
85
- @api.put_wall_post(process_result(fql))
86
-
108
+ @api = Koala::Facebook::API.new(oauth_access_token)
109
+ fql = @api.fql_query(my_fql_query)
110
+ @api.put_wall_post(process_result(fql))
111
+ ```
87
112
 
88
113
  OAuth
89
114
  -----
90
115
  You can use the Graph and REST APIs without an OAuth access token, but the real magic happens when you provide Facebook an OAuth token to prove you're authenticated. Koala provides an OAuth class to make that process easy:
91
-
92
- @oauth = Koala::Facebook::OAuth.new(app_id, app_secret, callback_url)
116
+ ```ruby
117
+ @oauth = Koala::Facebook::OAuth.new(app_id, app_secret, callback_url)
118
+ ```
93
119
 
94
120
  If your application uses Koala and the Facebook [JavaScript SDK](http://github.com/facebook/facebook-js-sdk) (formerly Facebook Connect), you can use the OAuth class to parse the cookies:
95
-
96
- @oauth.get_user_from_cookies(cookies) # gets the user's ID
97
- @oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
98
-
121
+ ```ruby
122
+ @oauth.get_user_from_cookies(cookies) # gets the user's ID
123
+ @oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
124
+ ```
99
125
  And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
100
-
101
- # generate authenticating URL
102
- @oauth.url_for_oauth_code
103
- # fetch the access token once you have the code
104
- @oauth.get_access_token(code)
126
+ ```ruby
127
+ # generate authenticating URL
128
+ @oauth.url_for_oauth_code
129
+ # fetch the access token once you have the code
130
+ @oauth.get_access_token(code)
131
+ ```
105
132
 
106
133
  You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
107
-
108
- @oauth.get_app_access_token
109
-
134
+ ```ruby
135
+ @oauth.get_app_access_token
136
+ ```
110
137
  For those building apps on Facebook, parsing signed requests is simple:
111
-
112
- @oauth.parse_signed_request(signed_request_string)
113
-
138
+ ```ruby
139
+ @oauth.parse_signed_request(signed_request_string)
140
+ ```
114
141
  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:
115
-
116
- @oauth.get_token_from_session_key(session_key)
117
- @oauth.get_tokens_from_session_keys(array_of_session_keys)
118
-
142
+ ```ruby
143
+ @oauth.get_token_from_session_key(session_key)
144
+ @oauth.get_tokens_from_session_keys(array_of_session_keys)
145
+ ```
119
146
  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
147
 
121
148
  Real-time Updates
@@ -123,50 +150,50 @@ Real-time Updates
123
150
  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.
124
151
 
125
152
  Koala makes it easy to interact with your applications using the RealtimeUpdates class:
126
-
127
- @updates = Koala::Facebook::RealtimeUpdates.new(:app_id => app_id, :secret => secret)
128
-
153
+ ```ruby
154
+ @updates = Koala::Facebook::RealtimeUpdates.new(:app_id => app_id, :secret => secret)
155
+ ```
129
156
  You can do just about anything with your real-time update subscriptions using the RealtimeUpdates class:
157
+ ```ruby
158
+ # Add/modify a subscription to updates for when the first_name or last_name fields of any of your users is changed
159
+ @updates.subscribe("user", "first_name, last_name", callback_url, verify_token)
130
160
 
131
- # Add/modify a subscription to updates for when the first_name or last_name fields of any of your users is changed
132
- @updates.subscribe("user", "first_name, last_name", callback_url, verify_token)
133
-
134
- # Get an array of your current subscriptions (one hash for each object you've subscribed to)
135
- @updates.list_subscriptions
136
-
137
- # Unsubscribe from updates for an object
138
- @updates.unsubscribe("user")
161
+ # Get an array of your current subscriptions (one hash for each object you've subscribed to)
162
+ @updates.list_subscriptions
139
163
 
164
+ # Unsubscribe from updates for an object
165
+ @updates.unsubscribe("user")
166
+ ```
140
167
  And to top it all off, RealtimeUpdates provides a static method to respond to Facebook servers' verification of your callback URLs:
141
-
142
- # Returns the hub.challenge parameter in params if the verify token in params matches verify_token
143
- Koala::Facebook::RealtimeUpdates.meet_challenge(params, your_verify_token)
144
-
168
+ ```ruby
169
+ # Returns the hub.challenge parameter in params if the verify token in params matches verify_token
170
+ Koala::Facebook::RealtimeUpdates.meet_challenge(params, your_verify_token)
171
+ ```
145
172
  For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
146
173
 
147
174
  Test Users
148
175
  -----
149
176
 
150
177
  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
-
178
+ ```ruby
179
+ @test_users = Koala::Facebook::TestUsers.new(:app_id => id, :secret => secret)
180
+ user = @test_users.create(is_app_installed, desired_permissions)
181
+ user_graph_api = Koala::Facebook::API.new(user["access_token"])
182
+ # or, if you want to make a whole community:
183
+ @test_users.create_network(network_size, is_app_installed, common_permissions)
184
+ ```
158
185
  Talking to Facebook
159
186
  -----
160
187
 
161
188
  Koala uses Faraday to make HTTP requests, which means you have complete control over how your app makes HTTP requests to Facebook. You can set Faraday options globally or pass them in on a per-request (or both):
162
-
163
- # Set an SSL certificate to avoid Net::HTTP errors
164
- Koala.http_service.http_options = {
165
- :ssl => { :ca_path => "/etc/ssl/certs" }
166
- }
167
- # or on a per-request basis
168
- @api.get_object(id, args_hash, { :timeout => 10 })
169
-
189
+ ```ruby
190
+ # Set an SSL certificate to avoid Net::HTTP errors
191
+ Koala.http_service.http_options = {
192
+ :ssl => { :ca_path => "/etc/ssl/certs" }
193
+ }
194
+ # or on a per-request basis
195
+ @api.get_object(id, args_hash, { :timeout => 10 })
196
+ ```
170
197
  The <a href="https://github.com/arsduo/koala/wiki/HTTP-Services">HTTP Services wiki page</a> has more information on what options are available, as well as on how to configure your own Faraday middleware stack (for instance, to implement request logging).
171
198
 
172
199
  See examples, ask questions
@@ -184,16 +211,16 @@ Testing
184
211
  -----
185
212
 
186
213
  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:
187
-
188
- # From anywhere in the project directory:
189
- bundle exec rake spec
190
-
214
+ ```bash
215
+ # From anywhere in the project directory:
216
+ bundle exec rake spec
217
+ ```
191
218
 
192
219
  You can also run live tests against Facebook's servers:
193
-
194
- # Again from anywhere in the project directory:
195
- LIVE=true bundle exec rake spec
196
- # you can also test against Facebook's beta tier
197
- LIVE=true BETA=true bundle exec rake spec
198
-
220
+ ```bash
221
+ # Again from anywhere in the project directory:
222
+ LIVE=true bundle exec rake spec
223
+ # you can also test against Facebook's beta tier
224
+ LIVE=true BETA=true bundle exec rake spec
225
+ ```
199
226
  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.
@@ -65,15 +65,15 @@ describe "Koala::Facebook::API" do
65
65
  end
66
66
 
67
67
  it "executes an error checking block if provided" do
68
- body = '{}'
69
- Koala.stub(:make_request).and_return(Koala::HTTPService::Response.new(200, body, {}))
68
+ response = Koala::HTTPService::Response.new(200, '{}', {})
69
+ Koala.stub(:make_request).and_return(response)
70
70
 
71
71
  yield_test = mock('Yield Tester')
72
72
  yield_test.should_receive(:pass)
73
73
 
74
74
  @service.api('anything', {}, "get") do |arg|
75
75
  yield_test.pass
76
- arg.should == MultiJson.load(body)
76
+ arg.should == response
77
77
  end
78
78
  end
79
79