Skip to content

Commit

Permalink
Implement Twitter::Cursor#each without making an extra HTTP request
Browse files Browse the repository at this point in the history
  • Loading branch information
sferik committed Jul 25, 2013
1 parent 6042913 commit 8eeff57
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 57 deletions.
12 changes: 6 additions & 6 deletions lib/twitter/api/friends_and_followers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module FriendsAndFollowers
# Twitter.friend_ids('sferik')
# Twitter.friend_ids(7505382) # Same as above
def friend_ids(*args)
cursor_from_response_with_user(:ids, nil, :get, "/1.1/friends/ids.json", args, :friend_ids)
cursor_from_response_with_user(:ids, nil, :get, "/1.1/friends/ids.json", args)
end

# @see https://dev.twitter.com/docs/api/1.1/get/followers/ids
Expand All @@ -57,7 +57,7 @@ def friend_ids(*args)
# Twitter.follower_ids('sferik')
# Twitter.follower_ids(7505382) # Same as above
def follower_ids(*args)
cursor_from_response_with_user(:ids, nil, :get, "/1.1/followers/ids.json", args, :follower_ids)
cursor_from_response_with_user(:ids, nil, :get, "/1.1/followers/ids.json", args)
end

# Returns the relationship of the authenticating user to the comma separated list of up to 100 screen_names or user_ids provided. Values for connections can be: following, following_requested, followed_by, none.
Expand Down Expand Up @@ -94,7 +94,7 @@ def friendships(*args)
# @example Return an array of numeric IDs for every user who has a pending request to follow the authenticating user
# Twitter.friendships_incoming
def friendships_incoming(options={})
cursor_from_response(:ids, nil, :get, "/1.1/friendships/incoming.json", options, :friendships_incoming)
cursor_from_response(:ids, nil, :get, "/1.1/friendships/incoming.json", options)
end

# Returns an array of numeric IDs for every protected user for whom the authenticating user has a pending follow request
Expand All @@ -109,7 +109,7 @@ def friendships_incoming(options={})
# @example Return an array of numeric IDs for every protected user for whom the authenticating user has a pending follow request
# Twitter.friendships_outgoing
def friendships_outgoing(options={})
cursor_from_response(:ids, nil, :get, "/1.1/friendships/outgoing.json", options, :friendships_outgoing)
cursor_from_response(:ids, nil, :get, "/1.1/friendships/outgoing.json", options)
end

# Allows the authenticating user to follow the specified users, unless they are already followed
Expand Down Expand Up @@ -280,7 +280,7 @@ def friendship?(source, target, options={})
# Twitter.followers('sferik')
# Twitter.followers(7505382) # Same as above
def followers(*args)
cursor_from_response_with_user(:users, Twitter::User, :get, "/1.1/followers/list.json", args, :followers)
cursor_from_response_with_user(:users, Twitter::User, :get, "/1.1/followers/list.json", args)
end

# Returns a cursored collection of user objects for every user the specified user is following (otherwise known as their "friends").
Expand Down Expand Up @@ -311,7 +311,7 @@ def followers(*args)
# Twitter.friends('sferik')
# Twitter.friends(7505382) # Same as above
def friends(*args)
cursor_from_response_with_user(:users, Twitter::User, :get, "/1.1/friends/list.json", args, :friends)
cursor_from_response_with_user(:users, Twitter::User, :get, "/1.1/friends/list.json", args)
end
alias following friends

Expand Down
14 changes: 7 additions & 7 deletions lib/twitter/api/lists.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def list_remove_member(*args)
# Twitter.memberships('sferik')
# Twitter.memberships(7505382)
def memberships(*args)
cursor_from_response_with_user(:lists, Twitter::List, :get, "/1.1/lists/memberships.json", args, :memberships)
cursor_from_response_with_user(:lists, Twitter::List, :get, "/1.1/lists/memberships.json", args)
end

# Returns the subscribers of the specified list
Expand All @@ -152,7 +152,7 @@ def memberships(*args)
# Twitter.list_subscribers('sferik', 8863586)
# Twitter.list_subscribers(7505382, 'presidents')
def list_subscribers(*args)
cursor_from_response_with_list(:get, "/1.1/lists/subscribers.json", args, :list_subscribers)
cursor_from_response_with_list(:get, "/1.1/lists/subscribers.json", args)
end

# Make the authenticated user follow the specified list
Expand Down Expand Up @@ -320,7 +320,7 @@ def list_member?(*args)
# Twitter.list_members(7505382, 'presidents')
# Twitter.list_members(7505382, 8863586)
def list_members(*args)
cursor_from_response_with_list(:get, "/1.1/lists/members.json", args, :list_members)
cursor_from_response_with_list(:get, "/1.1/lists/members.json", args)
end

# Add a member to a list
Expand Down Expand Up @@ -474,7 +474,7 @@ def list(*args)
# Twitter.subscriptions('sferik')
# Twitter.subscriptions(7505382)
def subscriptions(*args)
cursor_from_response_with_user(:lists, Twitter::List, :get, "/1.1/lists/subscriptions.json", args, :subscriptions)
cursor_from_response_with_user(:lists, Twitter::List, :get, "/1.1/lists/subscriptions.json", args)
end

# Removes specified members from the list
Expand Down Expand Up @@ -529,7 +529,7 @@ def list_remove_members(*args)
# Twitter.lists_owned('sferik')
# Twitter.lists_owned(7505382)
def lists_owned(*args)
cursor_from_response_with_user(:lists, Twitter::List, :get, "/1.1/lists/ownerships.json", args, :lists_owned)
cursor_from_response_with_user(:lists, Twitter::List, :get, "/1.1/lists/ownerships.json", args)
end
alias lists_ownerships lists_owned

Expand All @@ -546,11 +546,11 @@ def list_from_response(request_method, path, args)
object_from_response(Twitter::List, request_method, path, arguments.options)
end

def cursor_from_response_with_list(request_method, path, args, calling_method)
def cursor_from_response_with_list(request_method, path, args)
arguments = Twitter::API::Arguments.new(args)
merge_list!(arguments.options, arguments.pop)
merge_owner!(arguments.options, arguments.pop)
cursor_from_response(:users, Twitter::User, request_method, path, arguments.options, calling_method)
cursor_from_response(:users, Twitter::User, request_method, path, arguments.options)
end

def list_user?(request_method, path, args)
Expand Down
2 changes: 1 addition & 1 deletion lib/twitter/api/tweets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def oembeds(*args)
def retweeters_ids(*args)
arguments = Twitter::API::Arguments.new(args)
arguments.options[:id] ||= extract_id(arguments.first)
cursor_from_response(:ids, nil, :get, "/1.1/statuses/retweeters/ids.json", arguments.options, :retweeters_ids)
cursor_from_response(:ids, nil, :get, "/1.1/statuses/retweeters/ids.json", arguments.options)
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/twitter/api/undocumented.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module Undocumented
# Twitter.following_followers_of('sferik')
# Twitter.following_followers_of(7505382) # Same as above
def following_followers_of(*args)
cursor_from_response_with_user(:users, Twitter::User, :get, "/users/following_followers_of.json", args, :following_followers_of)
cursor_from_response_with_user(:users, Twitter::User, :get, "/users/following_followers_of.json", args)
end

# Returns Tweets count for a URL
Expand Down
4 changes: 2 additions & 2 deletions lib/twitter/api/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def update_profile_image(image, options={})
# @example Return an array of user objects that the authenticating user is blocking
# Twitter.blocking
def blocking(options={})
cursor_from_response(:users, Twitter::User, :get, "/1.1/blocks/list.json", options, :blocking)
cursor_from_response(:users, Twitter::User, :get, "/1.1/blocks/list.json", options)
end

# Returns an array of numeric user ids the authenticating user is blocking
Expand All @@ -170,7 +170,7 @@ def blocking(options={})
def blocked_ids(*args)
arguments = Twitter::API::Arguments.new(args)
merge_user!(arguments.options, arguments.pop)
cursor_from_response(:ids, nil, :get, "/1.1/blocks/ids.json", arguments.options, :blocked_ids)
cursor_from_response(:ids, nil, :get, "/1.1/blocks/ids.json", arguments.options)
end

# Returns true if the authenticating user is blocking a target user
Expand Down
10 changes: 4 additions & 6 deletions lib/twitter/api/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,25 +108,23 @@ def object_from_response(klass, request_method, path, options={})
# @param request_method [Symbol]
# @param path [String]
# @param args [Array]
# @param method_name [Symbol]
# @return [Twitter::Cursor]
def cursor_from_response_with_user(collection_name, klass, request_method, path, args, method_name)
def cursor_from_response_with_user(collection_name, klass, request_method, path, args)
arguments = Twitter::API::Arguments.new(args)
merge_user!(arguments.options, arguments.pop || screen_name) unless arguments.options[:user_id] || arguments.options[:screen_name]
cursor_from_response(collection_name, klass, request_method, path, arguments.options, method_name)
cursor_from_response(collection_name, klass, request_method, path, arguments.options)
end

# @param collection_name [Symbol]
# @param klass [Class]
# @param request_method [Symbol]
# @param path [String]
# @param options [Hash]
# @param method_name [Symbol]
# @return [Twitter::Cursor]
def cursor_from_response(collection_name, klass, request_method, path, options, method_name)
def cursor_from_response(collection_name, klass, request_method, path, options)
merge_default_cursor!(options)
response = send(request_method.to_sym, path, options)
Twitter::Cursor.from_response(response, collection_name.to_sym, klass, self, method_name, options)
Twitter::Cursor.from_response(response, collection_name.to_sym, klass, self, request_method, path, options)
end

def handle_forbidden_error(klass, error)
Expand Down
69 changes: 40 additions & 29 deletions lib/twitter/cursor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ class Cursor
# @param collection_name [String, Symbol] The name of the method to return the collection
# @param klass [Class] The class to instantiate object in the collection
# @param client [Twitter::Client]
# @param method_name [String, Symbol]
# @param method_options [Hash]
# @param request_method [String, Symbol]
# @param path [String]
# @param options [Hash]
# @return [Twitter::Cursor]
def self.from_response(response, collection_name, klass, client, method_name, method_options)
new(response[:body], collection_name, klass, client, method_name, method_options)
def self.from_response(response, collection_name, klass, client, request_method, path, options)
new(response[:body], collection_name, klass, client, request_method, path, options)
end

# Initializes a new Cursor
Expand All @@ -25,40 +26,38 @@ def self.from_response(response, collection_name, klass, client, method_name, me
# @param collection_name [String, Symbol] The name of the method to return the collection
# @param klass [Class] The class to instantiate object in the collection
# @param client [Twitter::Client]
# @param method_name [String, Symbol]
# @param method_options [Hash]
# @param request_method [String, Symbol]
# @param path [String]
# @param options [Hash]
# @return [Twitter::Cursor]
def initialize(attrs, collection_name, klass, client, method_name, method_options)
@attrs = attrs
def initialize(attrs, collection_name, klass, client, request_method, path, options)
@collection_name = collection_name.to_sym
@klass = klass
@client = client
@method_name = method_name
@method_options = method_options
@collection = Array(attrs[collection_name.to_sym]).map do |item|
if klass
klass.new(item)
else
item
end
end
@request_method = request_method.to_sym
@path = path
@options = options
set_attrs(attrs)
singleton_class.class_eval do
alias_method(collection_name.to_sym, :collection)
end
end

# @param collection [Array]
# @param cursor [Integer]
# @return [Array]
def all(collection=collection, cursor=next_cursor)
cursor = @client.send(@method_name.to_sym, @method_options.merge(:cursor => cursor))
collection += cursor.collection
cursor.last? ? collection.flatten : all(collection, cursor.next_cursor)
def all
map{|element| element}
end

# @return [Enumerable]
def each
all(collection, next_cursor).each do |element|
# @return [Enumerator]
def each(&block)
return to_enum(:each) unless block_given?
@collection.each do |element|
yield element
end
unless last?
fetch_next
each(&block)
end
self
end

def next_cursor
Expand All @@ -75,13 +74,25 @@ def previous_cursor
def first?
previous_cursor.zero?
end
alias first first?

# @return [Boolean]
def last?
next_cursor.zero?
end
alias last last?

private

def fetch_next
response = @client.send(@request_method, @path, @options.merge(:cursor => next_cursor))
set_attrs(response[:body])
end

def set_attrs(attrs)
@attrs = attrs
@collection = Array(attrs[@collection_name]).map do |element|
@klass ? @klass.new(element) : element
end
end

end
end
10 changes: 5 additions & 5 deletions spec/twitter/cursor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

describe "#collection" do
it "returns a collection" do
collection = Twitter::Cursor.new({:ids => [1, 2, 3, 4, 5]}, :ids, nil, Twitter::Client.new, :follower_ids, {}).collection
collection = Twitter::Cursor.new({:ids => [1, 2, 3, 4, 5]}, :ids, nil, Twitter::Client.new, :get, "/1.1/followers/ids.json", {}).collection
expect(collection).to be_an Array
expect(collection.first).to be_a Fixnum
end
Expand Down Expand Up @@ -48,15 +48,15 @@
describe "#first?" do
context "when previous cursor equals zero" do
before do
@cursor = Twitter::Cursor.new({:previous_cursor => 0}, :ids, nil, Twitter::Client.new, :follower_ids, {})
@cursor = Twitter::Cursor.new({:previous_cursor => 0}, :ids, nil, Twitter::Client.new, :get, "/1.1/followers/ids.json", {})
end
it "returns true" do
expect(@cursor.first?).to be_true
end
end
context "when previous cursor does not equal zero" do
before do
@cursor = Twitter::Cursor.new({:previous_cursor => 1}, :ids, nil, Twitter::Client.new, :follower_ids, {})
@cursor = Twitter::Cursor.new({:previous_cursor => 1}, :ids, nil, Twitter::Client.new, :get, "/1.1/followers/ids.json", {})
end
it "returns true" do
expect(@cursor.first?).to be_false
Expand All @@ -67,15 +67,15 @@
describe "#last?" do
context "when next cursor equals zero" do
before do
@cursor = Twitter::Cursor.new({:next_cursor => 0}, :ids, nil, Twitter::Client.new, :follower_ids, {})
@cursor = Twitter::Cursor.new({:next_cursor => 0}, :ids, nil, Twitter::Client.new, :get, "/1.1/followers/ids.json", {})
end
it "returns true" do
expect(@cursor.last?).to be_true
end
end
context "when next cursor does not equal zero" do
before do
@cursor = Twitter::Cursor.new({:next_cursor => 1}, :ids, nil, Twitter::Client.new, :follower_ids, {})
@cursor = Twitter::Cursor.new({:next_cursor => 1}, :ids, nil, Twitter::Client.new, :get, "/1.1/followers/ids.json", {})
end
it "returns false" do
expect(@cursor.last?).to be_false
Expand Down

0 comments on commit 8eeff57

Please sign in to comment.