koala 1.5.0 → 1.6.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/.travis.yml +4 -1
- data/Gemfile +1 -1
- data/changelog.md +293 -0
- data/koala.gemspec +3 -2
- data/lib/koala.rb +1 -2
- data/lib/koala/api.rb +11 -31
- data/lib/koala/api/batch_operation.rb +1 -1
- data/lib/koala/api/graph_api.rb +132 -62
- data/lib/koala/api/graph_batch_api.rb +28 -38
- data/lib/koala/api/graph_collection.rb +3 -1
- data/lib/koala/api/rest_api.rb +19 -3
- data/lib/koala/errors.rb +86 -0
- data/lib/koala/oauth.rb +21 -21
- data/lib/koala/realtime_updates.rb +42 -21
- data/lib/koala/version.rb +1 -1
- data/readme.md +130 -103
- data/spec/cases/api_spec.rb +3 -3
- data/spec/cases/error_spec.rb +91 -20
- data/spec/cases/graph_api_batch_spec.rb +57 -22
- data/spec/cases/graph_api_spec.rb +68 -0
- data/spec/cases/graph_collection_spec.rb +6 -0
- data/spec/cases/oauth_spec.rb +16 -16
- data/spec/cases/realtime_updates_spec.rb +80 -82
- data/spec/cases/test_users_spec.rb +21 -18
- data/spec/fixtures/mock_facebook_responses.yml +45 -29
- data/spec/spec_helper.rb +6 -6
- data/spec/support/graph_api_shared_examples.rb +13 -13
- data/spec/support/koala_test.rb +13 -13
- data/spec/support/rest_api_shared_examples.rb +3 -3
- metadata +30 -14
- data/CHANGELOG +0 -275
data/lib/koala/version.rb
CHANGED
data/readme.md
CHANGED
@@ -13,52 +13,78 @@ Installation
|
|
13
13
|
---
|
14
14
|
|
15
15
|
Easy:
|
16
|
-
|
17
|
-
|
16
|
+
```bash
|
17
|
+
[sudo|rvm] gem install koala
|
18
|
+
```
|
18
19
|
|
19
20
|
Or in Bundler:
|
20
|
-
|
21
|
-
|
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.
|
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
|
-
|
28
|
-
|
32
|
+
```ruby
|
33
|
+
@graph = Koala::Facebook::API.new(oauth_access_token)
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
39
|
+
# three-part queries are easy too!
|
40
|
+
@graph.get_connections("me", "mutualfriends/#{friend_id}")
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
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)
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
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
|
-
|
97
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
117
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
143
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
189
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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.
|
data/spec/cases/api_spec.rb
CHANGED
@@ -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
|
-
|
69
|
-
Koala.stub(:make_request).and_return(
|
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 ==
|
76
|
+
arg.should == response
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
data/spec/cases/error_spec.rb
CHANGED
@@ -1,40 +1,76 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Koala::Facebook::APIError do
|
4
|
-
it "is a
|
5
|
-
Koala::Facebook::APIError.new.should be_a(
|
4
|
+
it "is a Koala::KoalaError" do
|
5
|
+
Koala::Facebook::APIError.new(nil, nil).should be_a(Koala::KoalaError)
|
6
6
|
end
|
7
7
|
|
8
|
-
[:fb_error_type, :fb_error_code, :fb_error_message, :
|
8
|
+
[:fb_error_type, :fb_error_code, :fb_error_subcode, :fb_error_message, :http_status, :response_body].each do |accessor|
|
9
9
|
it "has an accessor for #{accessor}" do
|
10
10
|
Koala::Facebook::APIError.instance_methods.map(&:to_sym).should include(accessor)
|
11
11
|
Koala::Facebook::APIError.instance_methods.map(&:to_sym).should include(:"#{accessor}=")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
it "sets
|
16
|
-
error_response = {"type"
|
17
|
-
Koala::Facebook::APIError.new(error_response).
|
15
|
+
it "sets http_status to the provided status" do
|
16
|
+
error_response = '{ "error": {"type": "foo", "other_details": "bar"} }'
|
17
|
+
Koala::Facebook::APIError.new(400, error_response).response_body.should == error_response
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets response_body to the provided response body" do
|
21
|
+
Koala::Facebook::APIError.new(400, '').http_status.should == 400
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with an error_info hash" do
|
25
|
+
let(:error) {
|
26
|
+
error_info = {
|
27
|
+
'type' => 'type',
|
28
|
+
'message' => 'message',
|
29
|
+
'code' => 1,
|
30
|
+
'error_subcode' => 'subcode'
|
31
|
+
}
|
32
|
+
Koala::Facebook::APIError.new(400, '', error_info)
|
33
|
+
}
|
34
|
+
|
35
|
+
{
|
36
|
+
:fb_error_type => 'type',
|
37
|
+
:fb_error_message => 'message',
|
38
|
+
:fb_error_code => 1,
|
39
|
+
:fb_error_subcode => 'subcode'
|
40
|
+
}.each_pair do |accessor, value|
|
41
|
+
it "sets #{accessor} to #{value}" do
|
42
|
+
error.send(accessor).should == value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "sets the error message \"type: error_info['type'], code: error_info['code'], error_subcode: error_info['error_subcode'], message: error_info['message'] [HTTP http_status]\"" do
|
47
|
+
error.message.should == "type: type, code: 1, error_subcode: subcode, message: message [HTTP 400]"
|
48
|
+
end
|
18
49
|
end
|
19
50
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
it "sets #{accessor} to details['#{key}']" do
|
26
|
-
value = "foo"
|
27
|
-
Koala::Facebook::APIError.new(key => value).send(accessor).should == value
|
51
|
+
context "with an error_info string" do
|
52
|
+
it "sets the error message \"error_info [HTTP http_status]\"" do
|
53
|
+
error_info = "Facebook is down."
|
54
|
+
error = Koala::Facebook::APIError.new(400, '', error_info)
|
55
|
+
error.message.should == "Facebook is down. [HTTP 400]"
|
28
56
|
end
|
29
57
|
end
|
30
58
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
59
|
+
context "with no error_info and a response_body containing error JSON" do
|
60
|
+
it "should extract the error info from the response body" do
|
61
|
+
response_body = '{ "error": { "type": "type", "message": "message", "code": 1, "error_subcode": "subcode" } }'
|
62
|
+
error = Koala::Facebook::APIError.new(400, response_body)
|
63
|
+
{
|
64
|
+
:fb_error_type => 'type',
|
65
|
+
:fb_error_message => 'message',
|
66
|
+
:fb_error_code => 1,
|
67
|
+
:fb_error_subcode => 'subcode'
|
68
|
+
}.each_pair do |accessor, value|
|
69
|
+
error.send(accessor).should == value
|
70
|
+
end
|
71
|
+
end
|
37
72
|
end
|
73
|
+
|
38
74
|
end
|
39
75
|
|
40
76
|
describe Koala::KoalaError do
|
@@ -43,3 +79,38 @@ describe Koala::KoalaError do
|
|
43
79
|
end
|
44
80
|
end
|
45
81
|
|
82
|
+
describe Koala::Facebook::OAuthSignatureError do
|
83
|
+
it "is a Koala::KoalaError" do
|
84
|
+
Koala::KoalaError.new.should be_a(Koala::KoalaError)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe Koala::Facebook::BadFacebookResponse do
|
89
|
+
it "is a Koala::Facebook::APIError" do
|
90
|
+
Koala::Facebook::BadFacebookResponse.new(nil, nil).should be_a(Koala::Facebook::APIError)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe Koala::Facebook::OAuthTokenRequestError do
|
95
|
+
it "is a Koala::Facebook::APIError" do
|
96
|
+
Koala::Facebook::OAuthTokenRequestError.new(nil, nil).should be_a(Koala::Facebook::APIError)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe Koala::Facebook::ServerError do
|
101
|
+
it "is a Koala::Facebook::APIError" do
|
102
|
+
Koala::Facebook::ServerError.new(nil, nil).should be_a(Koala::Facebook::APIError)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe Koala::Facebook::ClientError do
|
107
|
+
it "is a Koala::Facebook::APIError" do
|
108
|
+
Koala::Facebook::ClientError.new(nil, nil).should be_a(Koala::Facebook::APIError)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe Koala::Facebook::AuthenticationError do
|
113
|
+
it "is a Koala::Facebook::ClientError" do
|
114
|
+
Koala::Facebook::AuthenticationError.new(nil, nil).should be_a(Koala::Facebook::ClientError)
|
115
|
+
end
|
116
|
+
end
|