koala 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +10 -0
- data/Manifest +16 -5
- data/Rakefile +1 -1
- data/koala.gemspec +4 -4
- data/lib/graph_api.rb +17 -12
- data/lib/http_services.rb +12 -2
- data/lib/koala.rb +50 -24
- data/lib/realtime_updates.rb +95 -0
- data/lib/rest_api.rb +7 -12
- data/readme.md +71 -25
- data/spec/facebook_data.yml +12 -3
- data/spec/koala/api_base_tests.rb +44 -0
- data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +10 -0
- data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +11 -0
- data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +101 -0
- data/spec/koala/{facebook_with_access_token_tests.rb → graph_api/graph_api_with_access_token_tests.rb} +40 -44
- data/spec/koala/live_testing_data_helper.rb +15 -0
- data/spec/koala/net_http_service_tests.rb +15 -0
- data/spec/koala/{facebook_oauth_tests.rb → oauth/oauth_tests.rb} +31 -13
- data/spec/koala/realtime_updates/realtime_updates_tests.rb +187 -0
- data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +94 -0
- data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +36 -0
- data/spec/koala_spec.rb +18 -38
- data/spec/koala_spec_helper.rb +30 -0
- data/spec/koala_spec_without_mocks.rb +19 -0
- data/spec/mock_facebook_responses.yml +208 -0
- data/spec/mock_http_service.rb +76 -0
- metadata +20 -8
- data/spec/koala/facebook_no_access_token_tests.rb +0 -88
- data/spec/koala/facebook_rest_api_no_access_token_test.rb +0 -20
- data/spec/koala/facebook_rest_api_with_access_token_test.rb +0 -33
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
v0.7.0
|
2
|
+
-- Added RealtimeUpdates class, which can be used to manage subscriptions for user updates (see http://developers.facebook.com/docs/api/realtime)
|
3
|
+
-- Added picture method to graph API, which fetches an object's picture from the redirect headers.
|
4
|
+
-- Added _greatly_ improved testing with result mocking, which is now the default set of tests
|
5
|
+
-- Renamed live testing spec to koala_spec_without_mocks.rb
|
6
|
+
-- Added Koala::Response class, which encapsulates HTTP results since Facebook sometimes sends data in the status or headers
|
7
|
+
-- Much internal refactoring
|
8
|
+
-- Updated readme, changelog, etc.
|
9
|
+
|
10
|
+
|
1
11
|
v0.6.0
|
2
12
|
-- Added support for the old REST API thanks to cbaclig's great work
|
3
13
|
-- Updated tests to conform to RSpec standards
|
data/Manifest
CHANGED
@@ -5,12 +5,23 @@ init.rb
|
|
5
5
|
lib/graph_api.rb
|
6
6
|
lib/http_services.rb
|
7
7
|
lib/koala.rb
|
8
|
+
lib/realtime_updates.rb
|
8
9
|
lib/rest_api.rb
|
9
10
|
readme.md
|
10
11
|
spec/facebook_data.yml
|
11
|
-
spec/koala/
|
12
|
-
spec/koala/
|
13
|
-
spec/koala/
|
14
|
-
spec/koala/
|
15
|
-
spec/koala/
|
12
|
+
spec/koala/api_base_tests.rb
|
13
|
+
spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb
|
14
|
+
spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb
|
15
|
+
spec/koala/graph_api/graph_api_no_access_token_tests.rb
|
16
|
+
spec/koala/graph_api/graph_api_with_access_token_tests.rb
|
17
|
+
spec/koala/live_testing_data_helper.rb
|
18
|
+
spec/koala/net_http_service_tests.rb
|
19
|
+
spec/koala/oauth/oauth_tests.rb
|
20
|
+
spec/koala/realtime_updates/realtime_updates_tests.rb
|
21
|
+
spec/koala/rest_api/rest_api_no_access_token_tests.rb
|
22
|
+
spec/koala/rest_api/rest_api_with_access_token_tests.rb
|
16
23
|
spec/koala_spec.rb
|
24
|
+
spec/koala_spec_helper.rb
|
25
|
+
spec/koala_spec_without_mocks.rb
|
26
|
+
spec/mock_facebook_responses.yml
|
27
|
+
spec/mock_http_service.rb
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ require 'rake'
|
|
4
4
|
require 'echoe'
|
5
5
|
|
6
6
|
# gem management
|
7
|
-
Echoe.new('koala', '0.
|
7
|
+
Echoe.new('koala', '0.7.0') do |p|
|
8
8
|
p.summary = "A lightweight, flexible library for Facebook's new Graph API"
|
9
9
|
p.description = "Koala is a lightweight, flexible Ruby SDK for Facebook's new Graph API. It allows read/write access to the Facebook Graph and provides OAuth URLs and cookie validation for Facebook Connect sites; it also supports access-token based interaction with the old REST API. Koala supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services."
|
10
10
|
p.url = "http://github.com/arsduo/koala"
|
data/koala.gemspec
CHANGED
@@ -2,15 +2,15 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{koala}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.7.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Alex Koppel, Chris Baclig, Rafi Jacoby, Context Optional"]
|
9
|
-
s.date = %q{2010-05-
|
9
|
+
s.date = %q{2010-05-26}
|
10
10
|
s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook's new Graph API. It allows read/write access to the Facebook Graph and provides OAuth URLs and cookie validation for Facebook Connect sites; it also supports access-token based interaction with the old REST API. Koala supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
|
11
11
|
s.email = %q{alex@alexkoppel.com}
|
12
|
-
s.extra_rdoc_files = ["CHANGELOG", "lib/graph_api.rb", "lib/http_services.rb", "lib/koala.rb", "lib/rest_api.rb"]
|
13
|
-
s.files = ["CHANGELOG", "Manifest", "Rakefile", "init.rb", "lib/graph_api.rb", "lib/http_services.rb", "lib/koala.rb", "lib/rest_api.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/graph_api.rb", "lib/http_services.rb", "lib/koala.rb", "lib/realtime_updates.rb", "lib/rest_api.rb"]
|
13
|
+
s.files = ["CHANGELOG", "Manifest", "Rakefile", "init.rb", "lib/graph_api.rb", "lib/http_services.rb", "lib/koala.rb", "lib/realtime_updates.rb", "lib/rest_api.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/api_base_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb", "spec/koala/graph_api/graph_api_no_access_token_tests.rb", "spec/koala/graph_api/graph_api_with_access_token_tests.rb", "spec/koala/live_testing_data_helper.rb", "spec/koala/net_http_service_tests.rb", "spec/koala/oauth/oauth_tests.rb", "spec/koala/realtime_updates/realtime_updates_tests.rb", "spec/koala/rest_api/rest_api_no_access_token_tests.rb", "spec/koala/rest_api/rest_api_with_access_token_tests.rb", "spec/koala_spec.rb", "spec/koala_spec_helper.rb", "spec/koala_spec_without_mocks.rb", "spec/mock_facebook_responses.yml", "spec/mock_http_service.rb", "koala.gemspec"]
|
14
14
|
s.homepage = %q{http://github.com/arsduo/koala}
|
15
15
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala", "--main", "readme.md"]
|
16
16
|
s.require_paths = ["lib"]
|
data/lib/graph_api.rb
CHANGED
@@ -32,19 +32,24 @@ module Koala
|
|
32
32
|
|
33
33
|
def get_object(id, args = {})
|
34
34
|
# Fetchs the given object from the graph.
|
35
|
-
|
35
|
+
graph_call(id, args)
|
36
36
|
end
|
37
37
|
|
38
38
|
def get_objects(ids, args = {})
|
39
39
|
# Fetchs all of the given object from the graph.
|
40
40
|
# We return a map from ID to object. If any of the IDs are invalid,
|
41
41
|
# we raise an exception.
|
42
|
-
|
42
|
+
graph_call("", args.merge("ids" => ids.join(",")))
|
43
43
|
end
|
44
44
|
|
45
45
|
def get_connections(id, connection_name, args = {})
|
46
46
|
# Fetchs the connections for given object.
|
47
|
-
|
47
|
+
graph_call("#{id}/#{connection_name}", args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_picture(object)
|
51
|
+
result = graph_call("#{object}/picture", {}, "get", :http_component => :headers)
|
52
|
+
result["Location"]
|
48
53
|
end
|
49
54
|
|
50
55
|
def put_object(parent_object, connection_name, args = {})
|
@@ -70,7 +75,7 @@ module Koala
|
|
70
75
|
# extended permissions.
|
71
76
|
|
72
77
|
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
73
|
-
|
78
|
+
graph_call("#{parent_object}/#{connection_name}", args, "post")
|
74
79
|
end
|
75
80
|
|
76
81
|
def put_wall_post(message, attachment = {}, profile_id = "me")
|
@@ -103,20 +108,20 @@ module Koala
|
|
103
108
|
|
104
109
|
def delete_object(id)
|
105
110
|
# Deletes the object with the given ID from the graph.
|
106
|
-
|
111
|
+
graph_call(id, {}, "delete")
|
107
112
|
end
|
108
113
|
|
109
114
|
def search(search_terms, args = {})
|
110
115
|
# Searches for a given term
|
111
|
-
|
116
|
+
graph_call("search", args.merge({:q => search_terms}))
|
112
117
|
end
|
113
118
|
|
114
|
-
def
|
115
|
-
response =
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
119
|
+
def graph_call(*args)
|
120
|
+
response = api(*args) do |response|
|
121
|
+
# check for Graph API-specific errors
|
122
|
+
if response.is_a?(Hash) && error_details = response["error"]
|
123
|
+
raise APIError.new(error_details)
|
124
|
+
end
|
120
125
|
end
|
121
126
|
|
122
127
|
response
|
data/lib/http_services.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
module Koala
|
2
|
+
class Response
|
3
|
+
attr_reader :status, :body, :headers
|
4
|
+
def initialize(status, body, headers)
|
5
|
+
@status = status
|
6
|
+
@body = body
|
7
|
+
@headers = headers
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
2
11
|
module NetHTTPService
|
3
12
|
# this service uses Net::HTTP to send requests to the graph
|
4
13
|
def self.included(base)
|
@@ -24,7 +33,7 @@ module Koala
|
|
24
33
|
|
25
34
|
result = http.start { |http|
|
26
35
|
response, body = (verb == "post" ? http.post(path, encode_params(args)) : http.get("#{path}?#{encode_params(args)}"))
|
27
|
-
body
|
36
|
+
Koala::Response.new(response.code.to_i, body, response.to_hash)
|
28
37
|
}
|
29
38
|
end
|
30
39
|
|
@@ -52,7 +61,8 @@ module Koala
|
|
52
61
|
# if the verb isn't get or post, send it as a post argument
|
53
62
|
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
54
63
|
server = options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER
|
55
|
-
self.send(verb, "https://#{server}/#{path}", :params => args)
|
64
|
+
response = self.send(verb, "https://#{server}/#{path}", :params => args)
|
65
|
+
Koala::Response.new(response.code, response.body, response.headers_hash)
|
56
66
|
end
|
57
67
|
end # class_eval
|
58
68
|
end
|
data/lib/koala.rb
CHANGED
@@ -14,6 +14,8 @@ require 'graph_api'
|
|
14
14
|
# add REST API methods
|
15
15
|
require 'rest_api'
|
16
16
|
|
17
|
+
require 'realtime_updates'
|
18
|
+
|
17
19
|
module Koala
|
18
20
|
|
19
21
|
module Facebook
|
@@ -44,19 +46,26 @@ module Koala
|
|
44
46
|
@access_token = access_token
|
45
47
|
end
|
46
48
|
|
47
|
-
def api(path, args = {}, verb = "get", options = {})
|
49
|
+
def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
|
48
50
|
# Fetches the given path in the Graph API.
|
49
|
-
args["access_token"] = @access_token if @access_token
|
50
|
-
|
51
|
+
args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
|
51
52
|
# make the request via the provided service
|
52
53
|
result = Koala.make_request(path, args, verb, options)
|
53
|
-
|
54
|
-
# Facebook sometimes sends results like "true" and "false", which aren't strictly object
|
55
|
-
# and cause JSON.parse to fail
|
56
|
-
# so we account for that
|
57
|
-
response = JSON.parse("[#{result}]")[0]
|
58
54
|
|
59
|
-
|
55
|
+
# Parse the body as JSON and check for errors if provided a mechanism to do so
|
56
|
+
# Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
|
57
|
+
# and cause JSON.parse to fail -- so we account for that by wrapping the result in []
|
58
|
+
body = response = JSON.parse("[#{result.body.to_s}]")[0]
|
59
|
+
if error_checking_block
|
60
|
+
yield(body)
|
61
|
+
end
|
62
|
+
|
63
|
+
# now return the desired information
|
64
|
+
if options[:http_component]
|
65
|
+
result.send(options[:http_component])
|
66
|
+
else
|
67
|
+
body
|
68
|
+
end
|
60
69
|
end
|
61
70
|
end
|
62
71
|
|
@@ -73,6 +82,10 @@ module Koala
|
|
73
82
|
include RestAPIMethods
|
74
83
|
end
|
75
84
|
|
85
|
+
class RealtimeUpdates < API
|
86
|
+
include RealtimeUpdateMethods
|
87
|
+
end
|
88
|
+
|
76
89
|
class APIError < Exception
|
77
90
|
attr_accessor :fb_error_type
|
78
91
|
def initialize(details = {})
|
@@ -83,7 +96,7 @@ module Koala
|
|
83
96
|
|
84
97
|
|
85
98
|
class OAuth
|
86
|
-
|
99
|
+
attr_reader :app_id, :app_secret, :oauth_callback_url
|
87
100
|
def initialize(app_id, app_secret, oauth_callback_url = nil)
|
88
101
|
@app_id = app_id
|
89
102
|
@app_secret = app_secret
|
@@ -144,6 +157,30 @@ module Koala
|
|
144
157
|
"https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
|
145
158
|
end
|
146
159
|
|
160
|
+
def get_access_token(code)
|
161
|
+
# convenience method to get a parsed token from Facebook for a given code
|
162
|
+
# should this require an OAuth callback URL?
|
163
|
+
get_token_from_server(:code => code, :redirect_uri => @oauth_callback_url)
|
164
|
+
end
|
165
|
+
|
166
|
+
def get_app_access_token
|
167
|
+
# convenience method to get a the application's sessionless access token
|
168
|
+
get_token_from_server({:type => 'client_cred'}, true)
|
169
|
+
end
|
170
|
+
|
171
|
+
protected
|
172
|
+
|
173
|
+
def get_token_from_server(args, post = false)
|
174
|
+
# fetch the result from Facebook's servers
|
175
|
+
result = fetch_token_string(args, post)
|
176
|
+
|
177
|
+
# if we have an error, parse the error JSON and raise an error
|
178
|
+
raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
|
179
|
+
|
180
|
+
# otherwise, parse the access token
|
181
|
+
parse_access_token(result)
|
182
|
+
end
|
183
|
+
|
147
184
|
def parse_access_token(response_text)
|
148
185
|
components = response_text.split("&").inject({}) do |hash, bit|
|
149
186
|
key, value = bit.split("=")
|
@@ -152,22 +189,11 @@ module Koala
|
|
152
189
|
components
|
153
190
|
end
|
154
191
|
|
155
|
-
def fetch_token_string(
|
192
|
+
def fetch_token_string(args, post = false)
|
156
193
|
Koala.make_request("oauth/access_token", {
|
157
194
|
:client_id => @app_id,
|
158
|
-
:
|
159
|
-
|
160
|
-
:code => code
|
161
|
-
}, "get")
|
162
|
-
end
|
163
|
-
|
164
|
-
def get_access_token(code)
|
165
|
-
result = fetch_token_string(code)
|
166
|
-
|
167
|
-
# if we have an error, parse the error JSON and raise an error
|
168
|
-
raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
|
169
|
-
# otherwise, parse the access token
|
170
|
-
parse_access_token(result)
|
195
|
+
:client_secret => @app_secret
|
196
|
+
}.merge!(args), post ? "post" : "get").body
|
171
197
|
end
|
172
198
|
end
|
173
199
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'koala'
|
2
|
+
|
3
|
+
module Koala
|
4
|
+
module Facebook
|
5
|
+
module RealtimeUpdateMethods
|
6
|
+
# note: to subscribe to real-time updates, you must have an application access token
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
# make the attributes readable
|
10
|
+
base.class_eval do
|
11
|
+
attr_reader :app_id, :app_access_token, :secret
|
12
|
+
|
13
|
+
# parses the challenge params and makes sure the call is legitimate
|
14
|
+
# returns the challenge string to be sent back to facebook if true
|
15
|
+
# returns false otherwise
|
16
|
+
# this is a class method, since you don't need to know anything about the app
|
17
|
+
# saves a potential trip fetching the app access token
|
18
|
+
def self.meet_challenge(params, verify_token = nil, &verification_block)
|
19
|
+
if params["hub.mode"] == "subscribe" &&
|
20
|
+
# you can make sure this is legitimate through two ways
|
21
|
+
# if your store the token across the calls, you can pass in the token value
|
22
|
+
# and we'll make sure it matches
|
23
|
+
(verify_token && params["hub.verify_token"] == verify_token) ||
|
24
|
+
# alternately, if you sent a specially-constructed value (such as a hash of various secret values)
|
25
|
+
# you can pass in a block, which we'll call with the verify_token sent by Facebook
|
26
|
+
# if it's legit, return anything that evaluates to true; otherwise, return nil or false
|
27
|
+
(verification_block && yield(params["hub.verify_token"]))
|
28
|
+
params["hub.challenge"]
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(options = {})
|
37
|
+
@app_id = options[:app_id]
|
38
|
+
@app_access_token = options[:app_access_token]
|
39
|
+
@secret = options[:secret]
|
40
|
+
unless @app_id && (@app_access_token || @secret) # make sure we have what we need
|
41
|
+
raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
|
42
|
+
end
|
43
|
+
|
44
|
+
# fetch the access token if we're provided a secret
|
45
|
+
if @secret && !@app_access_token
|
46
|
+
oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
47
|
+
@app_access_token = oauth.get_app_access_token["access_token"]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# subscribes for realtime updates
|
52
|
+
# your callback_url must be set up to handle the verification request or the subscription will not be set up
|
53
|
+
# http://developers.facebook.com/docs/api/realtime
|
54
|
+
def subscribe(object, fields, callback_url, verify_token)
|
55
|
+
args = {
|
56
|
+
:object => object,
|
57
|
+
:fields => fields,
|
58
|
+
:callback_url => callback_url,
|
59
|
+
:verify_token => verify_token
|
60
|
+
}
|
61
|
+
# a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
|
62
|
+
api(subscription_path, args, 'post', :http_component => :status) == 200
|
63
|
+
end
|
64
|
+
|
65
|
+
# removes subscription for object
|
66
|
+
# if object is nil, it will remove all subscriptions
|
67
|
+
def unsubscribe(object = nil)
|
68
|
+
args = {}
|
69
|
+
args[:object] = object if object
|
70
|
+
api(subscription_path, args, 'delete', :http_component => :status) == 200
|
71
|
+
end
|
72
|
+
|
73
|
+
def list_subscriptions
|
74
|
+
api(subscription_path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def api(*args) # same as GraphAPI
|
78
|
+
response = super(*args) do |response|
|
79
|
+
# check for subscription errors
|
80
|
+
if response.is_a?(Hash) && error_details = response["error"]
|
81
|
+
raise APIError.new(error_details)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
response
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def subscription_path
|
91
|
+
@subscription_path ||= "#{@app_id}/subscriptions"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/rest_api.rb
CHANGED
@@ -4,20 +4,15 @@ module Koala
|
|
4
4
|
|
5
5
|
module RestAPIMethods
|
6
6
|
def fql_query(fql)
|
7
|
-
|
8
|
-
"query" => fql,
|
9
|
-
"format" => "json",
|
10
|
-
}
|
11
|
-
|
12
|
-
api('method/fql.query', args, 'get', :rest_api => true)
|
7
|
+
rest_call('fql.query', 'query' => fql)
|
13
8
|
end
|
14
9
|
|
15
|
-
def
|
16
|
-
response =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
10
|
+
def rest_call(method, args = {})
|
11
|
+
response = api("method/#{method}", args.merge('format' => 'json'), 'get', :rest_api => true) do |response|
|
12
|
+
# check for REST API-specific errors
|
13
|
+
if response.is_a?(Hash) && response["error_code"]
|
14
|
+
raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
|
15
|
+
end
|
21
16
|
end
|
22
17
|
|
23
18
|
response
|
data/readme.md
CHANGED
@@ -1,53 +1,99 @@
|
|
1
1
|
Koala
|
2
2
|
====
|
3
|
-
Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.com/arsduo/koala</a>) is a new Facebook
|
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, the old REST API, realtime updates, and OAuth validation. We wrote Koala with four goals:
|
4
4
|
|
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
|
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 500 lines of code.)
|
6
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
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. (
|
9
|
-
|
10
|
-
Basic usage:
|
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.)
|
11
9
|
|
10
|
+
Graph API
|
11
|
+
----
|
12
|
+
The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
|
12
13
|
graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
|
13
14
|
profile = graph.get_object("me")
|
14
|
-
friends = graph.
|
15
|
+
friends = graph.get_connection("me", "friends")
|
15
16
|
graph.put_object("me", "feed", :message => "I am writing on my wall!")
|
16
17
|
|
17
|
-
|
18
|
-
[JavaScript SDK](http://github.com/facebook/connect-js), you can use the Koala::Facebook::OAuth class
|
19
|
-
to parse the cookies set by the JavaScript SDK for logged in users.
|
18
|
+
Check out the wiki for more examples.
|
20
19
|
|
21
|
-
|
20
|
+
The old-school REST API
|
22
21
|
-----
|
23
22
|
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 -- including fql.query -- that can't yet be done via the Graph.
|
24
23
|
|
25
|
-
Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the
|
24
|
+
Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
|
26
25
|
|
27
|
-
|
26
|
+
@rest = Koala::Facebook::RestAPI.new(oauth_access_token)
|
27
|
+
@rest.fql_query(my_fql_query) # convenience method
|
28
|
+
@rest.rest_call("stream.publish", arguments_hash) # generic version
|
29
|
+
|
30
|
+
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.
|
28
31
|
|
29
|
-
|
32
|
+
(If you want the power of both APIs in the palm of your hand, try out the GraphAndRestAPI class.)
|
30
33
|
|
31
|
-
|
34
|
+
OAuth
|
32
35
|
-----
|
33
|
-
|
36
|
+
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:
|
37
|
+
@oauth = Koala::Facebook::OAuth.new(app_id, code, callback_url)
|
34
38
|
|
35
|
-
|
39
|
+
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:
|
40
|
+
@oauth.get_user_from_cookie(cookies)
|
36
41
|
|
42
|
+
And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
|
43
|
+
# generate authenticating URL
|
44
|
+
@oauth.url_for_oauth_code
|
45
|
+
# fetch the access token once you have the code
|
46
|
+
@oauth.get_access_token(code)
|
37
47
|
|
38
|
-
|
48
|
+
You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
|
49
|
+
@oauth.get_app_access_token
|
50
|
+
|
51
|
+
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).
|
52
|
+
|
53
|
+
|
54
|
+
Real-time Updates
|
39
55
|
-----
|
56
|
+
The Graph API now allows your application to subscribe to real-time updates for certain objects in the graph.
|
40
57
|
|
41
|
-
|
58
|
+
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.
|
42
59
|
|
43
|
-
|
44
|
-
|
45
|
-
|
60
|
+
Koala makes it easy to interact with your applications using the RealTimeUpdates class:
|
61
|
+
|
62
|
+
@updates = Koala::Facebook::RealTimeUpdates.new(:app_id => app_id, :secret => secret)
|
63
|
+
|
64
|
+
You can do just about anything with your real-time update subscriptions using the RealTimeUpdates class:
|
65
|
+
|
66
|
+
# Add/modify a subscription to updates for when the first_name or last_name fields of any of your users is changed
|
67
|
+
@updates.subscribe("user", "first_name, last_name", callback_token, verify_token)
|
68
|
+
|
69
|
+
# Get an array of your current subscriptions (one hash for each object you've subscribed to)
|
70
|
+
@updates.list_subscriptions
|
46
71
|
|
47
|
-
|
72
|
+
# Unsubscribe from updates for an object
|
73
|
+
@updates.unsubscribe("user")
|
74
|
+
|
75
|
+
And to top it all off, RealTimeUpdates provides a static method to respond to Facebook servers' verification of your callback URLs:
|
76
|
+
|
77
|
+
# Returns the hub.challenge parameter in params if the verify token in params matches verify_token
|
78
|
+
Koala::Facebook::RealTimeUpdates.meet_challenge(params, your_verify_token)
|
79
|
+
|
80
|
+
For more information about meet_challenge and the RealTimeUpdates class, check out the Real-Time Updates page on the wiki.
|
81
|
+
|
82
|
+
See examples, ask questions
|
48
83
|
-----
|
49
|
-
|
84
|
+
Some resources to help you as you play with Koala and the Graph API:
|
85
|
+
|
86
|
+
* Complete Koala documentation <a href="http://wiki.github.com/arsduo/koala/">on the wiki</a>
|
87
|
+
* The <a href="http://groups.google.com/group/koala-users">Koala users group</a> on Google Groups, the place for your Koala and API questions
|
88
|
+
* The Koala-powered <a href="http://oauth.twoalex.com" target="_blank">OAuth Playground</a>, where you can easily generate OAuth access tokens and any other data needed to test out the APIs or OAuth
|
50
89
|
|
51
|
-
|
90
|
+
Testing
|
52
91
|
-----
|
53
|
-
|
92
|
+
|
93
|
+
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:
|
94
|
+
spec koala_tests.rb
|
95
|
+
|
96
|
+
You can also run live tests against Facebook's servers:
|
97
|
+
spec koala_tests_without_mocks.rb
|
98
|
+
|
99
|
+
Important Note: to run the live tests, you have to provide some of your own data: a valid OAuth access token with publish\_stream and read\_stream permissions and an OAuth code that can be used to generate an access token. You can get these data 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.)
|
data/spec/facebook_data.yml
CHANGED
@@ -5,18 +5,19 @@
|
|
5
5
|
|
6
6
|
# You must supply this value yourself to test the GraphAPI class.
|
7
7
|
# Your OAuth token should have publish_stream and read_stream permissions.
|
8
|
-
oauth_token:
|
8
|
+
oauth_token: 119908831367602|2.HlL6n4vuEhDKRyF_7dSipg__.3600.1274893200-2905623|NELx9TxQgEa2uL87wfg0arHIBvk.
|
9
9
|
|
10
10
|
# for testing the OAuth class
|
11
11
|
# baseline app
|
12
12
|
oauth_test_data:
|
13
13
|
# You must supply this value yourself, since they will expire.
|
14
|
-
code:
|
14
|
+
code: 2.HlL6n4vuEhDKRyF_7dSipg__.3600.1274893200-2905623|HgKssdjuTn4tMSStxS-LsMb_p4M.
|
15
15
|
|
16
16
|
# These values will work out of the box
|
17
17
|
app_id: 119908831367602
|
18
18
|
secret: e45e55a333eec232d4206d2703de1307
|
19
19
|
callback_url: http://oauth.twoalex.com/
|
20
|
+
app_access_token: 119908831367602|o3wswWQ88LYjEC9-ukR_gjRIOMw.
|
20
21
|
raw_token_string: "access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.&expires=6621"
|
21
22
|
raw_offline_access_token_string: access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.
|
22
23
|
valid_cookies:
|
@@ -27,4 +28,12 @@ oauth_test_data:
|
|
27
28
|
fbs_119908831367602: '"access_token=119908831367602|2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623|yVt5WH_S6J5p3gFa5_5lBzckhws.&expires=1273287600&secret=V_E79ovQnXqxGctFuC_n5A__&session_key=2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623&sig=eeef60838c0c800258d89b7e6ddddddb&uid=2905623"'
|
28
29
|
offline_access_cookies:
|
29
30
|
# note: I've revoked the offline access for security reasons, so you can't make calls against this :)
|
30
|
-
fbs_119908831367602: '"access_token=119908831367602|08170230801eb225068e7a70-2905623|Q3LDCYYF8CX9cstxnZLsxiR0nwg.&expires=0&secret=78abaee300b392e275072a9f2727d436&session_key=08170230801eb225068e7a70-2905623&sig=423b8aa4b6fa1f9a571955f8e929d567&uid=2905623"'
|
31
|
+
fbs_119908831367602: '"access_token=119908831367602|08170230801eb225068e7a70-2905623|Q3LDCYYF8CX9cstxnZLsxiR0nwg.&expires=0&secret=78abaee300b392e275072a9f2727d436&session_key=08170230801eb225068e7a70-2905623&sig=423b8aa4b6fa1f9a571955f8e929d567&uid=2905623"'
|
32
|
+
|
33
|
+
subscription_test_data:
|
34
|
+
subscription_path: http://oauth.twoalex.com/subscriptions
|
35
|
+
verify_token: "myverificationtoken|1f54545d5f722733e17faae15377928f"
|
36
|
+
challenge_data:
|
37
|
+
"hub.challenge": "1290024882"
|
38
|
+
"hub.verify_token": "myverificationtoken|1f54545d5f722733e17faae15377928f"
|
39
|
+
"hub.mode": "subscribe"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class ApiBaseTests < Test::Unit::TestCase
|
2
|
+
describe "Koala API base class" do
|
3
|
+
before(:each) do
|
4
|
+
@service = Koala::Facebook::API.new
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should not include an access token if none was given" do
|
8
|
+
Koala.should_receive(:make_request).with(
|
9
|
+
anything,
|
10
|
+
hash_not_including('access_token' => 1),
|
11
|
+
anything,
|
12
|
+
anything
|
13
|
+
).and_return(Koala::Response.new(200, "", ""))
|
14
|
+
|
15
|
+
@service.api('anything')
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should include an access token if given" do
|
19
|
+
token = 'adfadf'
|
20
|
+
service = Koala::Facebook::API.new token
|
21
|
+
|
22
|
+
Koala.should_receive(:make_request).with(
|
23
|
+
anything,
|
24
|
+
hash_including('access_token' => token),
|
25
|
+
anything,
|
26
|
+
anything
|
27
|
+
).and_return(Koala::Response.new(200, "", ""))
|
28
|
+
|
29
|
+
service.api('anything')
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should properly handle the http_component parameter"
|
33
|
+
|
34
|
+
it "should execute a block to test for errors if passed one"
|
35
|
+
|
36
|
+
it "should handle rogue true/false as responses" do
|
37
|
+
Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'true', {}))
|
38
|
+
@service.api('anything').should be_true
|
39
|
+
|
40
|
+
Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'false', {}))
|
41
|
+
@service.api('anything').should be_false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class GraphAndRestAPINoTokenTests < Test::Unit::TestCase
|
2
|
+
describe "Koala GraphAndRestAPI without an access token" do
|
3
|
+
before(:each) do
|
4
|
+
@api = Koala::Facebook::GraphAndRestAPI.new
|
5
|
+
end
|
6
|
+
|
7
|
+
it_should_behave_like "Koala RestAPI without an access token"
|
8
|
+
it_should_behave_like "Koala GraphAPI without an access token"
|
9
|
+
end
|
10
|
+
end
|