Advanced Twitter Queries with the Twitter Gem
Wednesday, February 25th, 2009
I wanted a reliable way to find out a few things about my twitter account (for example which of the users I am following are not following me back) - unfortunately the third party apps out there are not always very reliable, do not exactly do what I want/as I want so I decided to check out how easy it is to hack up a more advanced query using a Ruby twitter API wrapper. It turned out that it couldn’t be easier!
There are several gems wrapping the Twitter API out there - I started to use the ‘twitter’ gem from John Nunemaker and I am perfectly happy with it so far. John did a great job supporting all the features offered by the API - it’s a different question that the API, like twitter itself, is quite minimalistic. For example I have not found a way to get all my followers/friends easily (drop me a comment if I am missing something) so I monkey-patched this into the module in a generic way:
- module Twitter
- class Base
- def all_entries(method, options = {})
- all_entries = []
- next_100 = self.send method, {:page => (current_page = 1)}.merge(options)
- while (next_100.size != 0) do
- all_entries << next_100
- next_100 = self.send method, {:page => (current_page += 1)}.merge(options)
- end
- all_entries.flatten
- end
- end
- end
for example you can call connection.all_entries(:friends) to get all of your friends, given that you set up a connection to your account (I found only a method which returns your first 100 friends - didn’t spend too much time with the documentation though - agin, drop me a message if I overlooked something).
I have added a bit of syntactic sugar to be able to call connection.all_friends instead of connection.all_entries(:friends):
- module Twitter
- class Base
- alias_method :throw_method_missing, :method_missing
- def method_missing(method_name, *args, &bloke)
- if (method_name.to_s =~ /^all_.+/)
- all_entries(method_name.to_s[/all_(.+)/,1], args[0] || {})
- else
- throw_method_missing(method_name, *args, &bloke)
- end
- end
- end
- end
Here we ensure that only method calls that start with all_ are handled by all_entries, the rest is throwing a method_missing since we are not interested in handling those messages.
Now it could not be easier to implement the function I originally intended to build: list of users who are not following back.
- class Array
- def names
- self.map{|u| u.screen_name}
- end
- end
- module Twitter
- class Base
- def not_following_back
- all_friends.names - all_followers.names
- end
- end
- end
That’s all there’s to it (I am not a big fan of monkey patching core classes btw ; but in this case, adding the names() method to the Array class just made the method I intended to originally implement much cleaner so I rolled with it). Note that since subtraction is a non-commutative operation, all_friends.names - all_followers.names is not necessarily the same as allfollowers.names - allfriends.names. This is how the final code looks like:
- require ‘rubygems’
- require ‘twitter’
- connection = Twitter::Base.new(‘yourname’, ‘yourpass’)
- class Array
- def names
- self.map{|u| u.screen_name}
- end
- end
- module Twitter
- class Base
- alias_method :throw_method_missing, :method_missing
- def method_missing(method_name, *args, &bloke)
- if (method_name.to_s =~ /^all_.+/)
- all_entries(method_name.to_s[/all_(.+)/,1], args[0] || {})
- else
- throw_method_missing(method_name, *args, &bloke)
- end
- end
- def all_entries(method, options = {})
- all_entries = []
- next_100 = self.send method, {:page => (current_page = 1)}.merge(options)
- while (next_100.size != 0) do
- all_entries << next_100
- next_100 = self.send method, {:page => (current_page += 1)}.merge(options)
- end
- all_entries.flatten
- end
- def not_following_back
- all_friends.names - all_followers.names
- end
- end
- end
- p connection.not_following_back
You can download/check out the code here - do not try to copy & paste it from the text as it will be b0rk3d.
In part 2 I’d like to set up a small Sinatra app showing the above users in a list - displaying their avatar, screen name and real name, plus a link to remove them if you decide so.
I spent the first 2+ years in Ruby-land without even knowing about the probably most underused (and underrated) keyword of the language: redo. Even after I came across the first example and liked it immensely, I could not come up with another use for it, so I threw it to the bottom of my toolbox. Then I found another example, and another one - so I came to the conclusion that redo might be a valuable keyword in your Ruby arsenal after all - it is one of those things which you rarely need, but if you need it, it’s a perfect solution which would be cumbersome to replace with other constructs.