Permalink
Browse files

Changes to `Twitter::Client#follow` and `Twitter::Client#unfollow`

These methods now accept multiple users as arguments and return an array
instead of a `Twitter::User`. Additionally, the `Twitter::Client#follow`
now checks to make sure the user isn't already being followed. If you
don't wish to perform that check (which requires an extra HTTP request),
you can use the new `Twitter::Client#follow!` method instead.

Closes sferik/t#54.
  • Loading branch information...
1 parent d5d1f97 commit 24ffbca370f6957bc9a6c43cb6a1ee55cade7bb8 @sferik committed Jun 3, 2012
View
@@ -41,6 +41,14 @@ wiki][apps]!
The Active Support dependency has been removed!
+The `Twitter::Client#follow` and `Twitter::Client#unfollow` methods now accept
+multiple users as arguments and return an array instead of a `Twitter::User`.
+Additionally, the `Twitter::Client#follow` method now checks to make sure the
+user isn't already being followed. If you don't wish to perform that check
+(which requires an extra HTTP request), you can use the new
+`Twitter::Client#follow!` method instead. **Note**: This may re-send an email
+notification to the user, even if they are already being followed.
+
This version introduces an identity map, which ensures that the same objects
only get initialized once:
View
@@ -3,6 +3,7 @@
require 'twitter/config'
require 'twitter/configuration'
require 'twitter/connection'
+require 'twitter/core_ext/enumerable'
require 'twitter/core_ext/hash'
require 'twitter/cursor'
require 'twitter/direct_message'
@@ -655,7 +656,7 @@ def friendship(source, target, options={})
alias :friendship_show :friendship
alias :relationship :friendship
- # Allows the authenticating user to follow the specified user
+ # Allows the authenticating user to follow the specified users, unless they are already followed
#
# @see https://dev.twitter.com/docs/api/1/post/friendships/create
# @rate_limited No
@@ -667,31 +668,61 @@ def friendship(source, target, options={})
# @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
# @example Follow @sferik
# Twitter.follow('sferik')
- def follow(user, options={})
- options.merge_user!(user)
+ def follow(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
# Twitter always turns on notifications if the "follow" option is present, even if it's set to false
# so only send follow if it's true
options.merge!(:follow => true) if options.delete(:follow)
- user = post("/1/friendships/create.json", options)
- Twitter::User.new(user)
+ friend_ids = self.friend_ids.ids
+ user_ids = self.users(args).map(&:id)
+ (user_ids - friend_ids).threaded_map do |user|
+ user = post("/1/friendships/create.json", options.merge_user(user))
+ Twitter::User.new(user)
+ end
end
alias :friendship_create :follow
- # Allows the authenticating user to unfollow the specified user
+ # Allows the authenticating user to follow the specified users
+ #
+ # @see https://dev.twitter.com/docs/api/1/post/friendships/create
+ # @rate_limited No
+ # @requires_authentication Yes
+ # @param user [Integer, String, Twitter::User] A Twitter user ID, screen name, or object.
+ # @param options [Hash] A customizable set of options.
+ # @option options [Boolean] :follow (false) Enable notifications for the target user.
+ # @return [Array<Twitter::User>] The followed users.
+ # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
+ # @example Follow @sferik
+ # Twitter.follow!('sferik')
+ def follow!(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ # Twitter always turns on notifications if the "follow" option is present, even if it's set to false
+ # so only send follow if it's true
+ options.merge!(:follow => true) if options.delete(:follow)
+ args.threaded_map do |user|
+ user = post("/1/friendships/create.json", options.merge_user(user))
+ Twitter::User.new(user)
+ end
+ end
+ alias :friendship_create! :follow!
+
+ # Allows the authenticating user to unfollow the specified users
#
# @see https://dev.twitter.com/docs/api/1/post/friendships/destroy
# @rate_limited No
# @requires_authentication Yes
# @param user [Integer, String, Twitter::User] A Twitter user ID, screen name, or object.
# @param options [Hash] A customizable set of options.
- # @return [Twitter::User] The unfollowed user.
+ # @return [Array<Twitter::User>] The unfollowed users.
# @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
# @example Unfollow @sferik
# Twitter.unfollow('sferik')
- def unfollow(user, options={})
- options.merge_user!(user)
- user = delete("/1/friendships/destroy.json", options)
- Twitter::User.new(user)
+ def unfollow(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ args.threaded_map do |user|
+ user = delete("/1/friendships/destroy.json", options.merge_user(user))
+ Twitter::User.new(user)
+ end
end
alias :friendship_destroy :unfollow
@@ -25,7 +25,7 @@ def connection(options={})
:ssl => {:verify => false},
:url => options.fetch(:endpoint, endpoint),
}
- @connection ||=Faraday.new(default_options.deep_merge(connection_options)) do |builder|
+ @connection ||= Faraday.new(default_options.deep_merge(connection_options)) do |builder|
builder.use Twitter::Request::MultipartWithFile
builder.use Twitter::Request::TwitterOAuth, credentials if credentials?
builder.use Faraday::Request::Multipart
@@ -0,0 +1,11 @@
+module Enumerable
+
+ def threaded_map
+ threads = []
+ each do |object|
+ threads << Thread.new{yield object}
+ end
+ threads.map(&:value)
+ end
+
+end
@@ -68,6 +68,14 @@ def merge_owner!(user)
#
# @param user[Integer, String, Twitter::User] A Twitter user ID, screen_name, or object.
# @return [Hash]
+ def merge_user(user, prefix=nil, suffix=nil)
+ self.dup.merge_user!(user, prefix, suffix)
+ end
+
+ # Take a user and merge it into the hash with the correct key
+ #
+ # @param user[Integer, String, Twitter::User] A Twitter user ID, screen_name, or object.
+ # @return [Hash]
def merge_user!(user, prefix=nil, suffix=nil)
case user
when Integer
@@ -6,16 +6,16 @@ module Response
class ParseJson < Faraday::Response::Middleware
def parse(body)
- case body
- when ''
- nil
- when 'true'
- true
- when 'false'
- false
- else
- MultiJson.load(body)
- end
+ case body
+ when ''
+ nil
+ when 'true'
+ true
+ when 'false'
+ false
+ else
+ MultiJson.load(body)
+ end
end
def on_complete(env)
@@ -1 +1 @@
-{"previous_cursor_str":"0","next_cursor":0,"ids":[146197851,145833898,90678091,63267186,142867146,18443187,84722702,9607792,18589926,17831966,103213461,6734842,45322951,14517078,70353603,133143291,54368334,127522778,128046728,95206919,128441606,91078264,126052250,8285392,16314440],"previous_cursor":0,"next_cursor_str":"0"}
+{"previous_cursor_str":"0","next_cursor":0,"ids":[146197851,145833898,90678091,63267186,142867146,18443187,84722702,9607792,18589926,17831966,103213461,6734842,45322951,14517078,70353603,133143291,54368334,127522778,128046728,95206919,128441606,91078264,126052250,14100886,16314440],"previous_cursor":0,"next_cursor_str":"0"}
@@ -282,20 +282,117 @@
describe "#follow" do
context "with :follow => true passed" do
before do
+ stub_get("/1/friends/ids.json").
+ with(:query => {:cursor => "-1"}).
+ to_return(:body => fixture("id_list.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ stub_get("/1/users/lookup.json").
+ with(:query => {:screen_name => "sferik,pengwynn"}).
+ to_return(:body => fixture("friendships.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ stub_post("/1/friendships/create.json").
+ with(:body => {:user_id => "7505382", :follow => "true"}).
+ to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ end
+ it "should request the correct resource" do
+ @client.follow("sferik", "pengwynn", :follow => true)
+ a_get("/1/friends/ids.json").
+ with(:query => {:cursor => "-1"}).
+ should have_been_made
+ a_get("/1/users/lookup.json").
+ with(:query => {:screen_name => "sferik,pengwynn"}).
+ should have_been_made
+ a_post("/1/friendships/create.json").
+ with(:body => {:user_id => "7505382", :follow => "true"}).
+ should have_been_made
+ end
+ it "should return the befriended user" do
+ users = @client.follow("sferik", "pengwynn", :follow => true)
+ users.should be_an Array
+ users.first.should be_a Twitter::User
+ users.first.name.should == "Erik Michaels-Ober"
+ end
+ end
+ context "with :follow => false passed" do
+ before do
+ stub_get("/1/friends/ids.json").
+ with(:query => {:cursor => "-1"}).
+ to_return(:body => fixture("id_list.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ stub_get("/1/users/lookup.json").
+ with(:query => {:screen_name => "sferik,pengwynn"}).
+ to_return(:body => fixture("friendships.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ stub_post("/1/friendships/create.json").
+ with(:body => {:user_id => "7505382"}).
+ to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ end
+ it "should request the correct resource" do
+ @client.follow("sferik", "pengwynn", :follow => false)
+ a_get("/1/friends/ids.json").
+ with(:query => {:cursor => "-1"}).
+ should have_been_made
+ a_get("/1/users/lookup.json").
+ with(:query => {:screen_name => "sferik,pengwynn"}).
+ should have_been_made
+ a_post("/1/friendships/create.json").
+ with(:body => {:user_id => "7505382"}).
+ should have_been_made
+ end
+ it "should return the befriended user" do
+ users = @client.follow("sferik", "pengwynn", :follow => false)
+ users.should be_an Array
+ users.first.should be_a Twitter::User
+ users.first.name.should == "Erik Michaels-Ober"
+ end
+ end
+ context "without :follow passed" do
+ before do
+ stub_get("/1/friends/ids.json").
+ with(:query => {:cursor => "-1"}).
+ to_return(:body => fixture("id_list.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ stub_get("/1/users/lookup.json").
+ with(:query => {:screen_name => "sferik,pengwynn"}).
+ to_return(:body => fixture("friendships.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ stub_post("/1/friendships/create.json").
+ with(:body => {:user_id => "7505382"}).
+ to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
+ end
+ it "should request the correct resource" do
+ @client.follow("sferik", "pengwynn")
+ a_get("/1/friends/ids.json").
+ with(:query => {:cursor => "-1"}).
+ should have_been_made
+ a_get("/1/users/lookup.json").
+ with(:query => {:screen_name => "sferik,pengwynn"}).
+ should have_been_made
+ a_post("/1/friendships/create.json").
+ with(:body => {:user_id => "7505382"}).
+ should have_been_made
+ end
+ it "should return the befriended user" do
+ users = @client.follow("sferik", "pengwynn")
+ users.should be_an Array
+ users.first.should be_a Twitter::User
+ users.first.name.should == "Erik Michaels-Ober"
+ end
+ end
+ end
+
+ describe "#follow!" do
+ context "with :follow => true passed" do
+ before do
stub_post("/1/friendships/create.json").
with(:body => {:screen_name => "sferik", :follow => "true"}).
to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
end
it "should request the correct resource" do
- @client.follow("sferik", :follow => true)
+ @client.follow!("sferik", :follow => true)
a_post("/1/friendships/create.json").
with(:body => {:screen_name => "sferik", :follow => "true"}).
should have_been_made
end
it "should return the befriended user" do
- user = @client.follow("sferik", :follow => true)
- user.should be_a Twitter::User
- user.name.should == "Erik Michaels-Ober"
+ users = @client.follow!("sferik", :follow => true)
+ users.should be_an Array
+ users.first.should be_a Twitter::User
+ users.first.name.should == "Erik Michaels-Ober"
end
end
context "with :follow => false passed" do
@@ -305,15 +402,16 @@
to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
end
it "should request the correct resource" do
- @client.follow("sferik", :follow => false)
+ @client.follow!("sferik", :follow => false)
a_post("/1/friendships/create.json").
with(:body => {:screen_name => "sferik"}).
should have_been_made
end
it "should return the befriended user" do
- user = @client.follow("sferik", :follow => false)
- user.should be_a Twitter::User
- user.name.should == "Erik Michaels-Ober"
+ users = @client.follow!("sferik", :follow => false)
+ users.should be_an Array
+ users.first.should be_a Twitter::User
+ users.first.name.should == "Erik Michaels-Ober"
end
end
context "without :follow passed" do
@@ -323,15 +421,16 @@
to_return(:body => fixture("sferik.json"), :headers => {:content_type => "application/json; charset=utf-8"})
end
it "should request the correct resource" do
- @client.follow("sferik")
+ @client.follow!("sferik")
a_post("/1/friendships/create.json").
with(:body => {:screen_name => "sferik"}).
should have_been_made
end
it "should return the befriended user" do
- user = @client.follow("sferik")
- user.should be_a Twitter::User
- user.name.should == "Erik Michaels-Ober"
+ users = @client.follow!("sferik")
+ users.should be_an Array
+ users.first.should be_a Twitter::User
+ users.first.name.should == "Erik Michaels-Ober"
end
end
end
@@ -349,9 +448,10 @@
should have_been_made
end
it "should return the unfollowed" do
- user = @client.friendship_destroy("sferik")
- user.should be_a Twitter::User
- user.name.should == "Erik Michaels-Ober"
+ users = @client.friendship_destroy("sferik")
+ users.should be_an Array
+ users.first.should be_a Twitter::User
+ users.first.name.should == "Erik Michaels-Ober"
end
end

0 comments on commit 24ffbca

Please sign in to comment.