koala 1.1.0rc2 → 1.1.0rc3
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.
- data/.gitignore +1 -0
- data/.travis.yml +8 -0
- data/CHANGELOG +11 -6
- data/koala.gemspec +5 -5
- data/lib/koala.rb +10 -5
- data/lib/koala/batch_operation.rb +74 -0
- data/lib/koala/graph_api.rb +78 -117
- data/lib/koala/graph_batch_api.rb +87 -0
- data/lib/koala/graph_collection.rb +54 -0
- data/lib/koala/http_services.rb +5 -3
- data/lib/koala/http_services/net_http_service.rb +31 -26
- data/lib/koala/http_services/typhoeus_service.rb +4 -4
- data/lib/koala/oauth.rb +3 -3
- data/lib/koala/rest_api.rb +1 -1
- data/lib/koala/uploadable_io.rb +122 -90
- data/readme.md +6 -6
- data/spec/cases/api_base_spec.rb +2 -2
- data/spec/cases/graph_api_batch_spec.rb +171 -162
- data/spec/cases/http_services/http_service_spec.rb +27 -27
- data/spec/cases/http_services/net_http_service_spec.rb +169 -103
- data/spec/cases/oauth_spec.rb +1 -1
- data/spec/cases/realtime_updates_spec.rb +3 -3
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/mock_facebook_responses.yml +33 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/graph_api_shared_examples.rb +79 -35
- data/spec/support/mock_http_service.rb +3 -0
- data/spec/support/rest_api_shared_examples.rb +2 -2
- data/spec/support/setup_mocks_or_live.rb +1 -2
- metadata +10 -5
- data/lib/koala/graph_api_batch.rb +0 -151
data/spec/cases/oauth_spec.rb
CHANGED
@@ -391,7 +391,7 @@ describe "Koala::Facebook::OAuth" do
|
|
391
391
|
# the signed request code is ported directly from Facebook
|
392
392
|
# so we only need to test at a high level that it works
|
393
393
|
it "should throw an error if the algorithm is unsupported" do
|
394
|
-
|
394
|
+
MultiJson.stub(:decode).and_return("algorithm" => "my fun algorithm")
|
395
395
|
lambda { @oauth.parse_signed_request(@signed_request) }.should raise_error
|
396
396
|
end
|
397
397
|
|
@@ -43,19 +43,19 @@ describe "Koala::Facebook::RealtimeUpdates" do
|
|
43
43
|
it "should not allow write access to app_id" do
|
44
44
|
updates = Koala::Facebook::RealtimeUpdates.new(:app_id => @app_id, :app_access_token => @app_access_token)
|
45
45
|
# this should not throw errors
|
46
|
-
|
46
|
+
updates.methods.map(&:to_sym).should_not include(:app_id=)
|
47
47
|
end
|
48
48
|
|
49
49
|
it "should not allow write access to app_access_token" do
|
50
50
|
updates = Koala::Facebook::RealtimeUpdates.new(:app_id => @app_id, :app_access_token => @app_access_token)
|
51
51
|
# this should not throw errors
|
52
|
-
|
52
|
+
updates.methods.map(&:to_sym).should_not include(:app_access_token=)
|
53
53
|
end
|
54
54
|
|
55
55
|
it "should not allow write access to secret" do
|
56
56
|
updates = Koala::Facebook::RealtimeUpdates.new(:app_id => @app_id, :app_access_token => @app_access_token)
|
57
57
|
# this should not throw errors
|
58
|
-
|
58
|
+
updates.methods.map(&:to_sym).should_not include(:secret=)
|
59
59
|
end
|
60
60
|
|
61
61
|
# init with secret / fetching the token
|
Binary file
|
@@ -25,11 +25,11 @@ rest_api:
|
|
25
25
|
no_token: '{"error_code":104,"error_msg":"Requires valid signature","request_args":[{"key":"method","value":"fql.query"},{"key":"format","value":"json"},{"key":"query","value":"select read_stream from permissions where uid = 216743"}]}'
|
26
26
|
|
27
27
|
/method/fql.multiquery:
|
28
|
-
'queries
|
28
|
+
'queries=<%= MultiJson.encode({"query1" => "select post_id from stream where source_id = me()", "query2" => "select fromid from comment where post_id in (select post_id from #query1)", "query3" => "select uid, name from user where uid in (select fromid from #query2)"}) %>':
|
29
29
|
get:
|
30
30
|
with_token: '[{"name":"query1", "fql_result_set":[]},{"name":"query2", "fql_result_set":[]},{"name":"query3", "fql_result_set":[]}]'
|
31
31
|
no_token: '{"error_code":104,"error_msg":"Requires valid signature","request_args":[{"key":"method","value":"fql.query"},{"key":"format","value":"json"},{"key":"query","value":"select read_stream from permissions where uid = 216743"}]}'
|
32
|
-
'queries
|
32
|
+
'queries=<%= MultiJson.encode({"query1" => "select first_name from user where uid = 216743", "query2" => "select first_name from user where uid = 2905623"}) %>':
|
33
33
|
get:
|
34
34
|
with_token: '[{"name":"query1", "fql_result_set":[{"first_name":"Chris"}]},{"name":"query2", "fql_result_set":[{"first_name":"Alex"}]}]'
|
35
35
|
no_token: '[{"name":"query1", "fql_result_set":[{"first_name":"Chris"}]},{"name":"query2", "fql_result_set":[{"first_name":"Alex"}]}]'
|
@@ -74,52 +74,53 @@ graph_api:
|
|
74
74
|
with_token: '{"contextoptional":"{}","naitik":"{}"}'
|
75
75
|
no_token: '{"contextoptional":"{}","naitik":"{}"}'
|
76
76
|
# Ruby 1.8.7 and 1.9.2 generate JSON with different key ordering, hence we have to dynamically generate it here
|
77
|
-
batch=<%= [{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "koppel"}]
|
77
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "koppel"}]) %>:
|
78
78
|
post:
|
79
79
|
with_token: '[{"body":"{\"id\":\"123\"}"}, {"body":"{\"id\":\"456\"}"}]'
|
80
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/picture"}]
|
80
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/picture"}]) %>:
|
81
81
|
post:
|
82
82
|
with_token: '[{"headers":[{"name":"Location","value":"http://google.com"}]}]'
|
83
|
-
batch=<%= [{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "me/friends"}]
|
83
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "me/friends"}]) %>:
|
84
84
|
post:
|
85
85
|
with_token: '[{"body":"{\"id\":\"123\"}"}, {"body":"{\"data\":[]}"}]'
|
86
|
-
batch=<%= [{"method"=>"get", "relative_url"=>"me"}, {"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]
|
86
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"me"}, {"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]) %>:
|
87
87
|
post:
|
88
88
|
with_token: '[{"body":"{\"id\":\"123\"}"}, {"body":"{\"data\":[]}"}]'
|
89
|
-
batch=<%= [{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights"}, {"method"=>"get", "relative_url"=>"koppel?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]
|
89
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights"}, {"method"=>"get", "relative_url"=>"koppel?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]) %>:
|
90
90
|
post:
|
91
91
|
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},{"body":"{\"id\":\"123\"}"}]'
|
92
|
-
batch=<%= [{"method"=>"post", "relative_url"=>"FEED_ITEM_BATCH/likes"}, {"method"=>"delete", "relative_url"=> "FEED_ITEM_BATCH"}]
|
92
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"FEED_ITEM_BATCH/likes"}, {"method"=>"delete", "relative_url"=> "FEED_ITEM_BATCH"}]) %>:
|
93
93
|
post:
|
94
94
|
with_token: '[{"body": "{\"id\": \"MOCK_LIKE\"}"},{"body":true}]'
|
95
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]
|
95
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]) %>:
|
96
96
|
post:
|
97
97
|
with_token: '[null,{"body":"{}"}]'
|
98
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends", "omit_response_on_success" => false}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]
|
98
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends", "omit_response_on_success" => false}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]) %>:
|
99
99
|
post:
|
100
100
|
with_token: '[{"body":"{\"data\":[]}"},{"body":"{}"}]'
|
101
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/friends?limit=5"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=i-dont-exist:$.data.*.id}"}"}]
|
101
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/friends?limit=5"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=i-dont-exist:$.data.*.id}"}"}]) %>:
|
102
102
|
post:
|
103
103
|
with_token: '{"error":190,"error_description":"Error validating access token."}'
|
104
|
-
batch=<%= [{"method" => "post", "relative_url" => "method/fql.query", "body" => "query=select+name+from+user+where+uid%3D4"}]
|
104
|
+
batch=<%= MultiJson.encode([{"method" => "post", "relative_url" => "method/fql.query", "body" => "query=select+name+from+user+where+uid%3D4"}]) %>:
|
105
105
|
post:
|
106
106
|
with_token: '[{"body":"[{\"name\":\"Mark Zuckerberg\"}]"}]'
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
batch=<%= [{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights", "name" => "getdata"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getdata"}].to_json %>:
|
107
|
+
|
108
|
+
# dependencies
|
109
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"me", "name" => "getme"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getme"}]) %>:
|
110
|
+
post: &batch_dependent
|
111
|
+
with_token: '[null,{"body":"{\"id\":\"123\"}"}]'
|
112
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights", "name" => "getdata"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getdata"}]) %>:
|
114
113
|
post:
|
115
114
|
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},null]'
|
116
|
-
|
115
|
+
|
116
|
+
# attached files tests
|
117
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "op1_file0"}]) %>&op1_file0=[FILE]:
|
117
118
|
post:
|
118
119
|
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},null]'
|
119
|
-
batch=<%= [{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "
|
120
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "op1_file0"}]) %>&op1_file0=[FILE]:
|
120
121
|
post:
|
121
122
|
with_token: '[{"body":"{\"id\": \"MOCK_PHOTO\"}"}]'
|
122
|
-
batch=<%= [{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "
|
123
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "op1_file0"}, {"method"=>"post", "relative_url"=>"koppel/photos", "attached_files" => "op2_file0"}]) %>&op1_file0=[FILE]&op2_file0=[FILE]:
|
123
124
|
post:
|
124
125
|
with_token: '[{"body":"{\"id\": \"MOCK_PHOTO\"}"}, {"body":"{\"id\": \"MOCK_PHOTO\"}"}]'
|
125
126
|
|
@@ -161,6 +162,16 @@ graph_api:
|
|
161
162
|
post:
|
162
163
|
<<: *token_required
|
163
164
|
with_token: '{"id": "MOCK_PHOTO"}'
|
165
|
+
/me/videos:
|
166
|
+
source=[FILE]:
|
167
|
+
post:
|
168
|
+
<<: *token_required
|
169
|
+
with_token: '{"id": "MOCK_PHOTO"}'
|
170
|
+
message=This is the test message&source=[FILE]:
|
171
|
+
post:
|
172
|
+
<<: *token_required
|
173
|
+
with_token: '{"id": "MOCK_PHOTO"}'
|
174
|
+
|
164
175
|
/koppel:
|
165
176
|
no_args:
|
166
177
|
get:
|
data/spec/spec_helper.rb
CHANGED
@@ -166,44 +166,87 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
166
166
|
@temporary_object_id.should_not be_nil
|
167
167
|
end
|
168
168
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
result = @api.put_picture(file, content_type)
|
174
|
-
@temporary_object_id = result["id"]
|
175
|
-
@temporary_object_id.should_not be_nil
|
176
|
-
end
|
169
|
+
describe ".put_picture" do
|
170
|
+
it "should be able to post photos to the user's wall with an open file object" do
|
171
|
+
content_type = "image/jpg"
|
172
|
+
file = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
|
177
173
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
174
|
+
result = @api.put_picture(file, content_type)
|
175
|
+
@temporary_object_id = result["id"]
|
176
|
+
@temporary_object_id.should_not be_nil
|
177
|
+
end
|
178
|
+
|
179
|
+
it "uses the base HTTP service if the upload is a StringIO or similar" do
|
180
|
+
source = stub("UploadIO")
|
181
|
+
Koala::UploadableIO.stub(:new).and_return(source)
|
182
|
+
source.stub(:requires_base_http_service).and_return(true)
|
183
|
+
Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:http_service => Koala.base_http_service)).and_return(Koala::Response.new(200, "[]", {}))
|
184
|
+
@api.put_picture(StringIO.new)
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should be able to post photos to the user's wall without an open file object" do
|
188
|
+
content_type = "image/jpg",
|
189
|
+
file_path = File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg")
|
185
190
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
191
|
+
result = @api.put_picture(file_path, content_type)
|
192
|
+
@temporary_object_id = result["id"]
|
193
|
+
@temporary_object_id.should_not be_nil
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should be able to verify a photo posted to a user's wall" do
|
197
|
+
content_type = "image/jpg",
|
198
|
+
file_path = File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg")
|
199
|
+
|
200
|
+
expected_message = "This is the test message"
|
201
|
+
|
202
|
+
result = @api.put_picture(file_path, content_type, :message => expected_message)
|
203
|
+
@temporary_object_id = result["id"]
|
204
|
+
@temporary_object_id.should_not be_nil
|
205
|
+
|
206
|
+
get_result = @api.get_object(@temporary_object_id)
|
207
|
+
get_result["name"].should == expected_message
|
208
|
+
end
|
193
209
|
end
|
210
|
+
|
211
|
+
describe ".put_video" do
|
212
|
+
before :each do
|
213
|
+
@cat_movie = File.join(File.dirname(__FILE__), "..", "fixtures", "cat.m4v")
|
214
|
+
@content_type = "video/mpeg4"
|
215
|
+
end
|
194
216
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
@temporary_object_id = result["id"]
|
203
|
-
@temporary_object_id.should_not be_nil
|
217
|
+
it "should set options[:video] to true" do
|
218
|
+
source = stub("UploadIO")
|
219
|
+
Koala::UploadableIO.stub(:new).and_return(source)
|
220
|
+
source.stub(:requires_base_http_service).and_return(false)
|
221
|
+
Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:video => true)).and_return(Koala::Response.new(200, "[]", {}))
|
222
|
+
@api.put_video("foo")
|
223
|
+
end
|
204
224
|
|
205
|
-
|
206
|
-
|
225
|
+
it "should be able to post videos to the user's wall with an open file object" do
|
226
|
+
file = File.open(@cat_movie)
|
227
|
+
|
228
|
+
result = @api.put_video(file, @content_type)
|
229
|
+
@temporary_object_id = result["id"]
|
230
|
+
@temporary_object_id.should_not be_nil
|
231
|
+
end
|
232
|
+
|
233
|
+
it "uses the base HTTP service if the upload is a StringIO or similar" do
|
234
|
+
source = stub("UploadIO")
|
235
|
+
Koala::UploadableIO.stub(:new).and_return(source)
|
236
|
+
source.stub(:requires_base_http_service).and_return(true)
|
237
|
+
Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:http_service => Koala.base_http_service)).and_return(Koala::Response.new(200, "[]", {}))
|
238
|
+
@api.put_video(StringIO.new)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should be able to post videos to the user's wall without an open file object" do
|
242
|
+
result = @api.put_video(@cat_movie, @content_type)
|
243
|
+
@temporary_object_id = result["id"]
|
244
|
+
@temporary_object_id.should_not be_nil
|
245
|
+
end
|
246
|
+
|
247
|
+
# note: Facebook doesn't post videos immediately to the wall, due to processing time
|
248
|
+
# during which get_object(video_id) will return false
|
249
|
+
# hence we can't do the same verify test we do for photos
|
207
250
|
end
|
208
251
|
|
209
252
|
it "should be able to verify a message with an attachment posted to a feed" do
|
@@ -267,6 +310,7 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
267
310
|
:search => 3,
|
268
311
|
# methods that have special arguments
|
269
312
|
:put_picture => ["x.jpg", "image/jpg", {}, "me"],
|
313
|
+
:put_video => ["x.mp4", "video/mpeg4", {}, "me"],
|
270
314
|
:get_objects => [["x"], {}]
|
271
315
|
}.each_pair do |method_name, params|
|
272
316
|
it "should pass http options through for #{method_name}" do
|
@@ -343,8 +387,8 @@ shared_examples_for "Koala GraphAPI with GraphCollection" do
|
|
343
387
|
end
|
344
388
|
|
345
389
|
it "should have a read-only paging attribute" do
|
346
|
-
|
347
|
-
|
390
|
+
@result.methods.map(&:to_sym).should include(:paging)
|
391
|
+
@result.methods.map(&:to_sym).should_not include(:paging=)
|
348
392
|
end
|
349
393
|
|
350
394
|
describe "when getting a whole page" do
|
@@ -5,6 +5,9 @@ module Koala
|
|
5
5
|
module MockHTTPService
|
6
6
|
include Koala::HTTPService
|
7
7
|
|
8
|
+
# fix our specs to use ok_json, so we always get the same results from to_json
|
9
|
+
MultiJson.engine = :ok_json
|
10
|
+
|
8
11
|
# Mocks all HTTP requests for with koala_spec_with_mocks.rb
|
9
12
|
# Mocked values to be included in TEST_DATA used in specs
|
10
13
|
ACCESS_TOKEN = '*'
|
@@ -154,13 +154,13 @@ shared_examples_for "Koala RestAPI" do
|
|
154
154
|
"fql.multiquery", anything, anything
|
155
155
|
).and_return({})
|
156
156
|
|
157
|
-
@api.fql_multiquery
|
157
|
+
@api.fql_multiquery 'query string'
|
158
158
|
end
|
159
159
|
|
160
160
|
it "should pass a queries argument" do
|
161
161
|
queries = stub('query string')
|
162
162
|
queries_json = "some JSON"
|
163
|
-
|
163
|
+
MultiJson.stub(:encode).with(queries).and_return(queries_json)
|
164
164
|
|
165
165
|
@api.should_receive(:rest_call).with(
|
166
166
|
anything,
|
@@ -4,8 +4,7 @@ module KoalaTest
|
|
4
4
|
print "Validating permissions for live testing..."
|
5
5
|
# make sure we have the necessary permissions
|
6
6
|
api = Koala::Facebook::GraphAndRestAPI.new(token)
|
7
|
-
|
8
|
-
perms = api.fql_query("select read_stream, publish_stream, user_photos, read_insights from permissions where uid = #{uid}")[0]
|
7
|
+
perms = api.fql_query("select read_stream, publish_stream, user_photos, user_videos, read_insights from permissions where uid = me()")[0]
|
9
8
|
perms.each_pair do |perm, value|
|
10
9
|
if value == (perm == "read_insights" ? 1 : 0) # live testing depends on insights calls failing
|
11
10
|
puts "failed!\n" # put a new line after the print above
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: koala
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 5
|
5
|
-
version: 1.1.
|
5
|
+
version: 1.1.0rc3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Alex Koppel, Chris Baclig, Rafi Jacoby, Context Optional
|
@@ -10,11 +10,11 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-30 00:00:00 +02:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
17
|
+
name: multi_json
|
18
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
@@ -80,6 +80,7 @@ extra_rdoc_files:
|
|
80
80
|
files:
|
81
81
|
- .autotest
|
82
82
|
- .gitignore
|
83
|
+
- .travis.yml
|
83
84
|
- CHANGELOG
|
84
85
|
- Gemfile
|
85
86
|
- LICENSE
|
@@ -88,8 +89,10 @@ files:
|
|
88
89
|
- autotest/discover.rb
|
89
90
|
- koala.gemspec
|
90
91
|
- lib/koala.rb
|
92
|
+
- lib/koala/batch_operation.rb
|
91
93
|
- lib/koala/graph_api.rb
|
92
|
-
- lib/koala/
|
94
|
+
- lib/koala/graph_batch_api.rb
|
95
|
+
- lib/koala/graph_collection.rb
|
93
96
|
- lib/koala/http_services.rb
|
94
97
|
- lib/koala/http_services/net_http_service.rb
|
95
98
|
- lib/koala/http_services/typhoeus_service.rb
|
@@ -113,6 +116,7 @@ files:
|
|
113
116
|
- spec/cases/test_users_spec.rb
|
114
117
|
- spec/cases/uploadable_io_spec.rb
|
115
118
|
- spec/fixtures/beach.jpg
|
119
|
+
- spec/fixtures/cat.m4v
|
116
120
|
- spec/fixtures/facebook_data.yml
|
117
121
|
- spec/fixtures/mock_facebook_responses.yml
|
118
122
|
- spec/spec_helper.rb
|
@@ -139,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
143
|
requirements:
|
140
144
|
- - ">="
|
141
145
|
- !ruby/object:Gem::Version
|
142
|
-
hash:
|
146
|
+
hash: 1796660115517042142
|
143
147
|
segments:
|
144
148
|
- 0
|
145
149
|
version: "0"
|
@@ -171,6 +175,7 @@ test_files:
|
|
171
175
|
- spec/cases/test_users_spec.rb
|
172
176
|
- spec/cases/uploadable_io_spec.rb
|
173
177
|
- spec/fixtures/beach.jpg
|
178
|
+
- spec/fixtures/cat.m4v
|
174
179
|
- spec/fixtures/facebook_data.yml
|
175
180
|
- spec/fixtures/mock_facebook_responses.yml
|
176
181
|
- spec/spec_helper.rb
|
@@ -1,151 +0,0 @@
|
|
1
|
-
module Koala
|
2
|
-
module Facebook
|
3
|
-
class BatchOperation
|
4
|
-
attr_reader :access_token, :http_options, :post_processing, :files
|
5
|
-
|
6
|
-
def initialize(options = {})
|
7
|
-
@args = (options[:args] || {}).dup # because we modify it below
|
8
|
-
@access_token = options[:access_token]
|
9
|
-
@http_options = (options[:http_options] || {}).dup # dup because we modify it below
|
10
|
-
@batch_args = @http_options.delete(:batch_args) || {}
|
11
|
-
@url = options[:url]
|
12
|
-
@method = options[:method].to_sym
|
13
|
-
@post_processing = options[:post_processing]
|
14
|
-
|
15
|
-
process_binary_args
|
16
|
-
|
17
|
-
raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_batch_params(main_access_token)
|
21
|
-
# set up the arguments
|
22
|
-
args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
|
23
|
-
|
24
|
-
response = {
|
25
|
-
:method => @method,
|
26
|
-
:relative_url => @url,
|
27
|
-
}
|
28
|
-
|
29
|
-
# handle batch-level arguments, such as name, depends_on, and attached_files
|
30
|
-
@batch_args[:attached_files] = @files.keys.join(",") if @files
|
31
|
-
response.merge!(@batch_args) if @batch_args
|
32
|
-
|
33
|
-
# for get and delete, we append args to the URL string
|
34
|
-
# otherwise, they go in the body
|
35
|
-
if args_string.length > 0
|
36
|
-
if args_in_url?
|
37
|
-
response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
|
38
|
-
else
|
39
|
-
response[:body] = args_string if args_string.length > 0
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
response
|
44
|
-
end
|
45
|
-
|
46
|
-
protected
|
47
|
-
|
48
|
-
def process_binary_args
|
49
|
-
# collect binary files
|
50
|
-
@args.each_pair do |key, value|
|
51
|
-
if UploadableIO.binary_content?(value)
|
52
|
-
@files ||= {}
|
53
|
-
# we use object_id to ensure unique file identifiers across multiple batch operations
|
54
|
-
# remove it from the original hash and add it to the file store
|
55
|
-
id = "file#{GraphAPI.batch_calls.length}_#{@files.keys.length}"
|
56
|
-
@files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def args_in_url?
|
62
|
-
@method == :get || @method == :delete
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
module GraphAPIBatchMethods
|
67
|
-
def self.included(base)
|
68
|
-
base.class_eval do
|
69
|
-
# batch mode flags
|
70
|
-
def self.batch_mode?
|
71
|
-
!!@batch_mode
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.batch_calls
|
75
|
-
raise KoalaError, "GraphAPI.batch_calls accessed when not in batch block!" unless batch_mode?
|
76
|
-
@batch_calls
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.batch(http_options = {}, &block)
|
80
|
-
@batch_mode = true
|
81
|
-
@batch_http_options = http_options
|
82
|
-
@batch_calls = []
|
83
|
-
yield
|
84
|
-
begin
|
85
|
-
results = batch_api(@batch_calls)
|
86
|
-
ensure
|
87
|
-
@batch_mode = false
|
88
|
-
end
|
89
|
-
results
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.batch_api(batch_calls)
|
93
|
-
return [] unless batch_calls.length > 0
|
94
|
-
# Facebook requires a top-level access token
|
95
|
-
|
96
|
-
# Get the access token for the user and start building a hash to store params
|
97
|
-
# Turn the call args collected into what facebook expects
|
98
|
-
args = {}
|
99
|
-
access_token = args["access_token"] = batch_calls.first.access_token
|
100
|
-
args['batch'] = batch_calls.map { |batch_op|
|
101
|
-
args.merge!(batch_op.files) if batch_op.files
|
102
|
-
batch_op.to_batch_params(access_token)
|
103
|
-
}.to_json
|
104
|
-
|
105
|
-
# Make the POST request for the batch call
|
106
|
-
# batch operations have to go over SSL, but since there's an access token, that secures that
|
107
|
-
result = Koala.make_request('/', args, 'post', @batch_http_options)
|
108
|
-
# Raise an error if we get a 500
|
109
|
-
raise APIError.new("type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}") if result.status >= 500
|
110
|
-
|
111
|
-
response = JSON.parse(result.body.to_s)
|
112
|
-
# raise an error if we get a Batch API error message
|
113
|
-
raise APIError.new("type" => "Error #{response["error"]}", "message" => response["error_description"]) if response.is_a?(Hash) && response["error"]
|
114
|
-
|
115
|
-
# otherwise, map the results with post-processing included
|
116
|
-
index = 0 # keep compat with ruby 1.8 - no with_index for map
|
117
|
-
response.map do |call_result|
|
118
|
-
# Get the options hash
|
119
|
-
batch_op = batch_calls[index]
|
120
|
-
index += 1
|
121
|
-
|
122
|
-
if call_result
|
123
|
-
# (see note in regular api method about JSON parsing)
|
124
|
-
body = JSON.parse("[#{call_result['body'].to_s}]")[0]
|
125
|
-
unless call_result["code"].to_i >= 500 || error = GraphAPI.check_response(body)
|
126
|
-
# Get the HTTP component they want
|
127
|
-
data = case batch_op.http_options[:http_component]
|
128
|
-
when :status
|
129
|
-
call_result["code"].to_i
|
130
|
-
when :headers
|
131
|
-
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
132
|
-
call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
|
133
|
-
else
|
134
|
-
body
|
135
|
-
end
|
136
|
-
|
137
|
-
# process it if we are given a block to process with
|
138
|
-
batch_op.post_processing ? batch_op.post_processing.call(data) : data
|
139
|
-
else
|
140
|
-
error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
|
141
|
-
end
|
142
|
-
else
|
143
|
-
nil
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|