Skip to content

Commit

Permalink
Changes to Twitter::Client#follow and Twitter::Client#unfollow
Browse files Browse the repository at this point in the history
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-ruby#54.
  • Loading branch information
sferik committed Jun 3, 2012
1 parent d5d1f97 commit 24ffbca
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 38 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
53 changes: 42 additions & 11 deletions lib/twitter/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/twitter/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions lib/twitter/core_ext/enumerable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Enumerable

def threaded_map
threads = []
each do |object|
threads << Thread.new{yield object}
end
threads.map(&:value)
end

end
8 changes: 8 additions & 0 deletions lib/twitter/core_ext/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def merge_owner!(user)
self
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)
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.
Expand Down
20 changes: 10 additions & 10 deletions lib/twitter/response/parse_json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion spec/fixtures/id_list.json
Original file line number Diff line number Diff line change
@@ -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"}
130 changes: 115 additions & 15 deletions spec/twitter/client/friends_and_followers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -280,22 +280,119 @@
end

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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down

0 comments on commit 24ffbca

Please sign in to comment.