From 8eeff57f5c6d6ca0a6f1ff5ebc31e652a71fc150 Mon Sep 17 00:00:00 2001 From: Erik Michaels-Ober Date: Thu, 25 Jul 2013 11:00:09 +0200 Subject: [PATCH] Implement Twitter::Cursor#each without making an extra HTTP request --- lib/twitter/api/friends_and_followers.rb | 12 ++--- lib/twitter/api/lists.rb | 14 ++--- lib/twitter/api/tweets.rb | 2 +- lib/twitter/api/undocumented.rb | 2 +- lib/twitter/api/users.rb | 4 +- lib/twitter/api/utils.rb | 10 ++-- lib/twitter/cursor.rb | 69 ++++++++++++++---------- spec/twitter/cursor_spec.rb | 10 ++-- 8 files changed, 66 insertions(+), 57 deletions(-) diff --git a/lib/twitter/api/friends_and_followers.rb b/lib/twitter/api/friends_and_followers.rb index 8640cfd44..1cb6124bd 100644 --- a/lib/twitter/api/friends_and_followers.rb +++ b/lib/twitter/api/friends_and_followers.rb @@ -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 @@ -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. @@ -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 @@ -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 @@ -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"). @@ -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 diff --git a/lib/twitter/api/lists.rb b/lib/twitter/api/lists.rb index 7ef921734..77c14ac92 100644 --- a/lib/twitter/api/lists.rb +++ b/lib/twitter/api/lists.rb @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) diff --git a/lib/twitter/api/tweets.rb b/lib/twitter/api/tweets.rb index 8e5aefbdc..f748d2a1e 100644 --- a/lib/twitter/api/tweets.rb +++ b/lib/twitter/api/tweets.rb @@ -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 diff --git a/lib/twitter/api/undocumented.rb b/lib/twitter/api/undocumented.rb index 68c9391fe..310cfb7b3 100644 --- a/lib/twitter/api/undocumented.rb +++ b/lib/twitter/api/undocumented.rb @@ -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 diff --git a/lib/twitter/api/users.rb b/lib/twitter/api/users.rb index 0c4650e6b..19815e7df 100644 --- a/lib/twitter/api/users.rb +++ b/lib/twitter/api/users.rb @@ -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 @@ -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 diff --git a/lib/twitter/api/utils.rb b/lib/twitter/api/utils.rb index d24755083..92e5b6910 100644 --- a/lib/twitter/api/utils.rb +++ b/lib/twitter/api/utils.rb @@ -108,12 +108,11 @@ 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] @@ -121,12 +120,11 @@ def cursor_from_response_with_user(collection_name, klass, request_method, path, # @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) diff --git a/lib/twitter/cursor.rb b/lib/twitter/cursor.rb index 33bcfc38c..a1b58bd38 100644 --- a/lib/twitter/cursor.rb +++ b/lib/twitter/cursor.rb @@ -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 @@ -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 @@ -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 diff --git a/spec/twitter/cursor_spec.rb b/spec/twitter/cursor_spec.rb index fc6432cf4..dc1279593 100644 --- a/spec/twitter/cursor_spec.rb +++ b/spec/twitter/cursor_spec.rb @@ -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 @@ -48,7 +48,7 @@ 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 @@ -56,7 +56,7 @@ 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 @@ -67,7 +67,7 @@ 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 @@ -75,7 +75,7 @@ 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