tyler_koala 1.2.0beta
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +12 -0
- data/.gitignore +5 -0
- data/.travis.yml +9 -0
- data/CHANGELOG +185 -0
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/Manifest +39 -0
- data/Rakefile +16 -0
- data/autotest/discover.rb +1 -0
- data/koala.gemspec +50 -0
- data/lib/koala.rb +119 -0
- data/lib/koala/batch_operation.rb +74 -0
- data/lib/koala/graph_api.rb +281 -0
- data/lib/koala/graph_batch_api.rb +87 -0
- data/lib/koala/graph_collection.rb +54 -0
- data/lib/koala/http_service.rb +161 -0
- data/lib/koala/oauth.rb +181 -0
- data/lib/koala/realtime_updates.rb +89 -0
- data/lib/koala/rest_api.rb +95 -0
- data/lib/koala/test_users.rb +102 -0
- data/lib/koala/uploadable_io.rb +180 -0
- data/lib/koala/utils.rb +7 -0
- data/readme.md +160 -0
- data/spec/cases/api_base_spec.rb +101 -0
- data/spec/cases/error_spec.rb +30 -0
- data/spec/cases/graph_and_rest_api_spec.rb +48 -0
- data/spec/cases/graph_api_batch_spec.rb +600 -0
- data/spec/cases/graph_api_spec.rb +42 -0
- data/spec/cases/http_service_spec.rb +420 -0
- data/spec/cases/koala_spec.rb +21 -0
- data/spec/cases/oauth_spec.rb +428 -0
- data/spec/cases/realtime_updates_spec.rb +198 -0
- data/spec/cases/rest_api_spec.rb +41 -0
- data/spec/cases/test_users_spec.rb +281 -0
- data/spec/cases/uploadable_io_spec.rb +206 -0
- data/spec/cases/utils_spec.rb +8 -0
- data/spec/fixtures/beach.jpg +0 -0
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/facebook_data.yml +61 -0
- data/spec/fixtures/mock_facebook_responses.yml +439 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/graph_api_shared_examples.rb +502 -0
- data/spec/support/json_testing_fix.rb +42 -0
- data/spec/support/koala_test.rb +163 -0
- data/spec/support/mock_http_service.rb +98 -0
- data/spec/support/ordered_hash.rb +205 -0
- data/spec/support/rest_api_shared_examples.rb +285 -0
- data/spec/support/uploadable_io_shared_examples.rb +70 -0
- metadata +221 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Koala
|
2
|
+
module Facebook
|
3
|
+
class BatchOperation
|
4
|
+
attr_reader :access_token, :http_options, :post_processing, :files, :batch_api, :identifier
|
5
|
+
|
6
|
+
@identifier = 0
|
7
|
+
|
8
|
+
def self.next_identifier
|
9
|
+
@identifier += 1
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@identifier = self.class.next_identifier
|
14
|
+
@args = (options[:args] || {}).dup # because we modify it below
|
15
|
+
@access_token = options[:access_token]
|
16
|
+
@http_options = (options[:http_options] || {}).dup # dup because we modify it below
|
17
|
+
@batch_args = @http_options.delete(:batch_args) || {}
|
18
|
+
@url = options[:url]
|
19
|
+
@method = options[:method].to_sym
|
20
|
+
@post_processing = options[:post_processing]
|
21
|
+
|
22
|
+
process_binary_args
|
23
|
+
|
24
|
+
raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_batch_params(main_access_token)
|
28
|
+
# set up the arguments
|
29
|
+
args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
|
30
|
+
|
31
|
+
response = {
|
32
|
+
:method => @method.to_s,
|
33
|
+
:relative_url => @url,
|
34
|
+
}
|
35
|
+
|
36
|
+
# handle batch-level arguments, such as name, depends_on, and attached_files
|
37
|
+
@batch_args[:attached_files] = @files.keys.join(",") if @files
|
38
|
+
response.merge!(@batch_args) if @batch_args
|
39
|
+
|
40
|
+
# for get and delete, we append args to the URL string
|
41
|
+
# otherwise, they go in the body
|
42
|
+
if args_string.length > 0
|
43
|
+
if args_in_url?
|
44
|
+
response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
|
45
|
+
else
|
46
|
+
response[:body] = args_string if args_string.length > 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
response
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def process_binary_args
|
56
|
+
# collect binary files
|
57
|
+
@args.each_pair do |key, value|
|
58
|
+
if UploadableIO.binary_content?(value)
|
59
|
+
@files ||= {}
|
60
|
+
# we use a class-level counter to ensure unique file identifiers across multiple batch operations
|
61
|
+
# (this is thread safe, since we just care about uniqueness)
|
62
|
+
# so remove the file from the original hash and add it to the file store
|
63
|
+
id = "op#{identifier}_file#{@files.keys.length}"
|
64
|
+
@files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def args_in_url?
|
70
|
+
@method == :get || @method == :delete
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
module Koala
|
2
|
+
module Facebook
|
3
|
+
GRAPH_SERVER = "graph.facebook.com"
|
4
|
+
|
5
|
+
module GraphAPIMethods
|
6
|
+
# A client for the Facebook Graph API.
|
7
|
+
#
|
8
|
+
# See http://github.com/arsduo/koala for Ruby/Koala documentation
|
9
|
+
# and http://developers.facebook.com/docs/api for Facebook API documentation
|
10
|
+
#
|
11
|
+
# The Graph API is made up of the objects in Facebook (e.g., people, pages,
|
12
|
+
# events, photos) and the connections between them (e.g., friends,
|
13
|
+
# photo tags, and event RSVPs). This client provides access to those
|
14
|
+
# primitive types in a generic way. For example, given an OAuth access
|
15
|
+
# token, this will fetch the profile of the active user and the list
|
16
|
+
# of the user's friends:
|
17
|
+
#
|
18
|
+
# graph = Koala::Facebook::API.new(access_token)
|
19
|
+
# user = graph.get_object("me")
|
20
|
+
# friends = graph.get_connections(user["id"], "friends")
|
21
|
+
#
|
22
|
+
# You can see a list of all of the objects and connections supported
|
23
|
+
# by the API at http://developers.facebook.com/docs/reference/api/.
|
24
|
+
#
|
25
|
+
# You can obtain an access token via OAuth or by using the Facebook
|
26
|
+
# JavaScript SDK. See the Koala and Facebook documentation for more information.
|
27
|
+
#
|
28
|
+
# If you are using the JavaScript SDK, you can use the
|
29
|
+
# Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
|
30
|
+
# for the active user from the cookie saved by the SDK.
|
31
|
+
|
32
|
+
# Objects
|
33
|
+
|
34
|
+
def get_object(id, args = {}, options = {})
|
35
|
+
# Fetchs the given object from the graph.
|
36
|
+
graph_call(id, args, "get", options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_objects(ids, args = {}, options = {})
|
40
|
+
# Fetchs all of the given objects from the graph.
|
41
|
+
# If any of the IDs are invalid, they'll raise an exception.
|
42
|
+
return [] if ids.empty?
|
43
|
+
graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def put_object(parent_object, connection_name, args = {}, options = {})
|
47
|
+
# Writes the given object to the graph, connected to the given parent.
|
48
|
+
# See http://developers.facebook.com/docs/api#publishing for all of
|
49
|
+
# the supported writeable objects.
|
50
|
+
#
|
51
|
+
# For example,
|
52
|
+
# graph.put_object("me", "feed", :message => "Hello, world")
|
53
|
+
# writes "Hello, world" to the active user's wall.
|
54
|
+
#
|
55
|
+
# Most write operations require extended permissions. For example,
|
56
|
+
# publishing wall posts requires the "publish_stream" permission. See
|
57
|
+
# http://developers.facebook.com/docs/authentication/ for details about
|
58
|
+
# extended permissions.
|
59
|
+
|
60
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
61
|
+
graph_call("#{parent_object}/#{connection_name}", args, "post", options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete_object(id, options = {})
|
65
|
+
# Deletes the object with the given ID from the graph.
|
66
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
|
67
|
+
graph_call(id, {}, "delete", options)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Connections
|
71
|
+
|
72
|
+
def get_connections(id, connection_name, args = {}, options = {})
|
73
|
+
# Fetchs the connections for given object.
|
74
|
+
graph_call("#{id}/#{connection_name}", args, "get", options) do |result|
|
75
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def put_connections(id, connection_name, args = {}, options = {})
|
80
|
+
# Posts a certain connection
|
81
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
82
|
+
graph_call("#{id}/#{connection_name}", args, "post", options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete_connections(id, connection_name, args = {}, options = {})
|
86
|
+
# Deletes a given connection
|
87
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
|
88
|
+
graph_call("#{id}/#{connection_name}", args, "delete", options)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Media (photos and videos)
|
92
|
+
# to delete photos or videos, use delete_object(object_id)
|
93
|
+
# note: you'll need the user_photos or user_videos permissions to actually access media after upload
|
94
|
+
|
95
|
+
def get_picture(object, args = {}, options = {})
|
96
|
+
# Gets a picture object, returning the URL (which Facebook sends as a header)
|
97
|
+
graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
|
98
|
+
result["Location"]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Can be called in multiple ways:
|
103
|
+
#
|
104
|
+
# put_picture(file, [content_type], ...)
|
105
|
+
# put_picture(path_to_file, [content_type], ...)
|
106
|
+
# put_picture(picture_url, ...)
|
107
|
+
#
|
108
|
+
# You can pass in uploaded files directly from Rails or Sinatra.
|
109
|
+
# (See lib/koala/uploadable_io.rb for supported frameworks)
|
110
|
+
#
|
111
|
+
# Optional parameters can be added to the end of the argument list:
|
112
|
+
# - args: a hash of request parameters (default: {})
|
113
|
+
# - target_id: ID of the target where to post the picture (default: "me")
|
114
|
+
# - options: a hash of http options passed to the HTTPService module
|
115
|
+
#
|
116
|
+
# put_picture(file, content_type, {:message => "Message"}, 01234560)
|
117
|
+
# put_picture(params[:file], {:message => "Message"})
|
118
|
+
#
|
119
|
+
# (Note that with URLs, there's no optional content type field)
|
120
|
+
# put_picture(picture_url, {:message => "Message"}, my_page_id)
|
121
|
+
|
122
|
+
def put_picture(*picture_args)
|
123
|
+
put_object(*parse_media_args(picture_args, "photos"))
|
124
|
+
end
|
125
|
+
|
126
|
+
def put_video(*video_args)
|
127
|
+
args = parse_media_args(video_args, "videos")
|
128
|
+
args.last[:video] = true
|
129
|
+
put_object(*args)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Wall posts
|
133
|
+
# To get wall posts, use get_connections(user, "feed")
|
134
|
+
# To delete a wall post, just use delete_object(post_id)
|
135
|
+
|
136
|
+
def put_wall_post(message, attachment = {}, profile_id = "me", options = {})
|
137
|
+
# attachment is a hash describing the wall post
|
138
|
+
# (see X for more details)
|
139
|
+
# For instance,
|
140
|
+
#
|
141
|
+
# {"name" => "Link name"
|
142
|
+
# "link" => "http://www.example.com/",
|
143
|
+
# "caption" => "{*actor*} posted a new review",
|
144
|
+
# "description" => "This is a longer description of the attachment",
|
145
|
+
# "picture" => "http://www.example.com/thumbnail.jpg"}
|
146
|
+
|
147
|
+
self.put_object(profile_id, "feed", attachment.merge({:message => message}), options)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Comments
|
151
|
+
# to delete comments, use delete_object(comment_id)
|
152
|
+
# to get comments, use get_connections(object, "likes")
|
153
|
+
|
154
|
+
def put_comment(object_id, message, options = {})
|
155
|
+
# Writes the given comment on the given post.
|
156
|
+
self.put_object(object_id, "comments", {:message => message}, options)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Likes
|
160
|
+
# to get likes, use get_connections(user, "likes")
|
161
|
+
|
162
|
+
def put_like(object_id, options = {})
|
163
|
+
# Likes the given post.
|
164
|
+
self.put_object(object_id, "likes", {}, options)
|
165
|
+
end
|
166
|
+
|
167
|
+
def delete_like(object_id, options = {})
|
168
|
+
# Unlikes a given object for the logged-in user
|
169
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
|
170
|
+
graph_call("#{object_id}/likes", {}, "delete", options)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Search
|
174
|
+
|
175
|
+
def search(search_terms, args = {}, options = {})
|
176
|
+
args.merge!({:q => search_terms}) unless search_terms.nil?
|
177
|
+
graph_call("search", args, "get", options) do |result|
|
178
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Convenience Methods
|
183
|
+
|
184
|
+
def get_page_access_token(object_id)
|
185
|
+
result = get_object(object_id, :fields => "access_token") do
|
186
|
+
result ? result["access_token"] : nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def get_comments_for_urls(urls = [], args = {}, options = {})
|
191
|
+
# Fetchs the comments for given URLs (array or comma-separated string)
|
192
|
+
# see https://developers.facebook.com/blog/post/490
|
193
|
+
return [] if urls.empty?
|
194
|
+
args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
|
195
|
+
get_object("comments", args, options)
|
196
|
+
end
|
197
|
+
|
198
|
+
# GraphCollection support
|
199
|
+
def get_page(params)
|
200
|
+
# Pages through a set of results stored in a GraphCollection
|
201
|
+
# Used for connections and search results
|
202
|
+
graph_call(*params) do |result|
|
203
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Batch API
|
208
|
+
def batch(http_options = {}, &block)
|
209
|
+
batch_client = GraphBatchAPI.new(access_token)
|
210
|
+
if block
|
211
|
+
yield batch_client
|
212
|
+
batch_client.execute(http_options)
|
213
|
+
else
|
214
|
+
batch_client
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.included(base)
|
219
|
+
base.class_eval do
|
220
|
+
def self.batch
|
221
|
+
raise NoMethodError, "The BatchAPI signature has changed (the original implementation was not thread-safe). Please see https://github.com/arsduo/koala/wiki/Batch-requests. (This message will be removed in the final 1.1 release.)"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Direct access to the Facebook API
|
227
|
+
# see any of the above methods for example invocations
|
228
|
+
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
229
|
+
result = api(path, args, verb, options) do |response|
|
230
|
+
error = check_response(response)
|
231
|
+
raise error if error
|
232
|
+
end
|
233
|
+
|
234
|
+
# now process as appropriate (get picture header, make GraphCollection, etc.)
|
235
|
+
post_processing ? post_processing.call(result) : result
|
236
|
+
end
|
237
|
+
|
238
|
+
def check_response(response)
|
239
|
+
# check for Graph API-specific errors
|
240
|
+
# this returns an error, which is immediately raised (non-batch)
|
241
|
+
# or added to the list of batch results (batch)
|
242
|
+
if response.is_a?(Hash) && error_details = response["error"]
|
243
|
+
APIError.new(error_details)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
def parse_media_args(media_args, method)
|
250
|
+
# photo and video uploads can accept different types of arguments (see above)
|
251
|
+
# so here, we parse the arguments into a form directly usable in put_object
|
252
|
+
raise KoalaError.new("Wrong number of arguments for put_#{method == "photos" ? "picture" : "video"}") unless media_args.size.between?(1, 5)
|
253
|
+
|
254
|
+
args_offset = media_args[1].kind_of?(Hash) || media_args.size == 1 ? 0 : 1
|
255
|
+
|
256
|
+
args = media_args[1 + args_offset] || {}
|
257
|
+
target_id = media_args[2 + args_offset] || "me"
|
258
|
+
options = media_args[3 + args_offset] || {}
|
259
|
+
|
260
|
+
if url?(media_args.first)
|
261
|
+
# If media_args is a URL, we can upload without UploadableIO
|
262
|
+
args.merge!(:url => media_args.first)
|
263
|
+
else
|
264
|
+
args["source"] = Koala::UploadableIO.new(*media_args.slice(0, 1 + args_offset))
|
265
|
+
end
|
266
|
+
|
267
|
+
[target_id, method, args, options]
|
268
|
+
end
|
269
|
+
|
270
|
+
def url?(data)
|
271
|
+
return false unless data.is_a? String
|
272
|
+
begin
|
273
|
+
uri = URI.parse(data)
|
274
|
+
%w( http https ).include?(uri.scheme)
|
275
|
+
rescue URI::BadURIError
|
276
|
+
false
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Koala
|
2
|
+
module Facebook
|
3
|
+
module GraphBatchAPIMethods
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
alias_method :graph_call_outside_batch, :graph_call
|
8
|
+
alias_method :graph_call, :graph_call_in_batch
|
9
|
+
|
10
|
+
alias_method :check_graph_api_response, :check_response
|
11
|
+
alias_method :check_response, :check_graph_batch_api_response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def batch_calls
|
16
|
+
@batch_calls ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
|
20
|
+
# for batch APIs, we queue up the call details (incl. post-processing)
|
21
|
+
batch_calls << BatchOperation.new(
|
22
|
+
:url => path,
|
23
|
+
:args => args,
|
24
|
+
:method => verb,
|
25
|
+
:access_token => options['access_token'] || access_token,
|
26
|
+
:http_options => options,
|
27
|
+
:post_processing => post_processing
|
28
|
+
)
|
29
|
+
nil # batch operations return nothing immediately
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_graph_batch_api_response(response)
|
33
|
+
if response.is_a?(Hash) && response["error"] && !response["error"].is_a?(Hash)
|
34
|
+
APIError.new("type" => "Error #{response["error"]}", "message" => response["error_description"])
|
35
|
+
else
|
36
|
+
check_graph_api_response(response)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def execute(http_options = {})
|
41
|
+
return [] unless batch_calls.length > 0
|
42
|
+
# Turn the call args collected into what facebook expects
|
43
|
+
args = {}
|
44
|
+
args["batch"] = MultiJson.encode(batch_calls.map { |batch_op|
|
45
|
+
args.merge!(batch_op.files) if batch_op.files
|
46
|
+
batch_op.to_batch_params(access_token)
|
47
|
+
})
|
48
|
+
|
49
|
+
graph_call_outside_batch('/', args, 'post', http_options) do |response|
|
50
|
+
# map the results with post-processing included
|
51
|
+
index = 0 # keep compat with ruby 1.8 - no with_index for map
|
52
|
+
response.map do |call_result|
|
53
|
+
# Get the options hash
|
54
|
+
batch_op = batch_calls[index]
|
55
|
+
index += 1
|
56
|
+
|
57
|
+
if call_result
|
58
|
+
# (see note in regular api method about JSON parsing)
|
59
|
+
body = MultiJson.decode("[#{call_result['body'].to_s}]")[0]
|
60
|
+
|
61
|
+
unless call_result["code"].to_i >= 500 || error = check_response(body)
|
62
|
+
# Get the HTTP component they want
|
63
|
+
data = case batch_op.http_options[:http_component]
|
64
|
+
when :status
|
65
|
+
call_result["code"].to_i
|
66
|
+
when :headers
|
67
|
+
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
68
|
+
call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
|
69
|
+
else
|
70
|
+
body
|
71
|
+
end
|
72
|
+
|
73
|
+
# process it if we are given a block to process with
|
74
|
+
batch_op.post_processing ? batch_op.post_processing.call(data) : data
|
75
|
+
else
|
76
|
+
error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
|
77
|
+
end
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Koala
|
2
|
+
module Facebook
|
3
|
+
class GraphCollection < Array
|
4
|
+
# This class is a light wrapper for collections returned
|
5
|
+
# from the Graph API.
|
6
|
+
#
|
7
|
+
# It extends Array to allow direct access to the data colleciton
|
8
|
+
# which should allow it to drop in seamlessly.
|
9
|
+
#
|
10
|
+
# It also allows access to paging information and the
|
11
|
+
# ability to get the next/previous page in the collection
|
12
|
+
# by calling next_page or previous_page.
|
13
|
+
attr_reader :paging
|
14
|
+
attr_reader :api
|
15
|
+
|
16
|
+
def initialize(response, api)
|
17
|
+
super response["data"]
|
18
|
+
@paging = response["paging"]
|
19
|
+
@api = api
|
20
|
+
end
|
21
|
+
|
22
|
+
# defines methods for NEXT and PREVIOUS pages
|
23
|
+
%w{next previous}.each do |this|
|
24
|
+
|
25
|
+
# def next_page
|
26
|
+
# def previous_page
|
27
|
+
define_method "#{this.to_sym}_page" do
|
28
|
+
base, args = send("#{this}_page_params")
|
29
|
+
base ? @api.get_page([base, args]) : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# def next_page_params
|
33
|
+
# def previous_page_params
|
34
|
+
define_method "#{this.to_sym}_page_params" do
|
35
|
+
return nil unless @paging and @paging[this]
|
36
|
+
parse_page_url(@paging[this])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_page_url(url)
|
41
|
+
match = url.match(/.com\/(.*)\?(.*)/)
|
42
|
+
base = match[1]
|
43
|
+
args = match[2]
|
44
|
+
params = CGI.parse(args)
|
45
|
+
new_params = {}
|
46
|
+
params.each_pair do |key,value|
|
47
|
+
new_params[key] = value.join ","
|
48
|
+
end
|
49
|
+
[base,new_params]
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|