Advanced Twitter Queries with the Twitter Gem

twitter_post.pngI 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 all_followers.names – all_friends.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.

Ruby’s Most Underused Keyword

redo.pngI 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.

Break vs Next vs Redo

I am sure everyone is familiar with redo’s cousins: next and break. While next and break allow you to skip an iteration, resp. skip the rest of the remaining iterations altogether, redo does not skip anything – it ‘restarts’ the same iteration. In other words, while next transfers control to the end of the iteration block, redo ‘jumps’ to the beginning of the block. Let’s see an example, where redo is used to recover from an input error (code from “The Ruby Programming Language”):

puts "Please enter the first word you can think of"
words =%w(apple banana cherry)
response = words.collect do |word|
  print word + "> "
  response = gets.chop
  if response.size == 0
    word.upcase!
    redo
  end
  response
end

The trick is that whenever the user enters an empty string, the block is restarted with redo, asking the same ‘question’ until the user enters something that makes sense to the system.

A Real Example – Tail Recursion

As of now, Ruby does not support tail recursion optimization, so you either have to write the function in a non-recursive (i.e. iterative) way yourself, or suffer the consequences of a full stack… or use redo!

I recently came across Magnus Holm’s article on this problem. Magnus provides an elegant solution using redo:

define_method(:acc) do |i, n, result|
  if i == -1
    result
  else
    i, n, result = i - 1, n + result, n
    redo
  end
end

def fib(i)
  acc(i, 1, 0)
end

Magnus even worked around a redo perk: “The redo statement restarts the current iteration of a loop or iterator”. That means he couldn’t define a method with “def”, since that neither involves a loop nor an iterator. Maybe a less esoteric way relying on the same fact would be to use a lambda:

def fib(i)
  acc = lambda do |i, n, result|
    if i == -1
      result
    else
      i, n, result = i - 1, n + result, n
      redo
    end
  end.call(i, 1, 0)
end

Golf Time

Here is a Ruby golf riddle, which I have no idea how could be made this short (42 characters) without redo (via coderrr):

"Generate a random string of length 4 or more consisting of only the following 
set of characters: a-z, A-Z, 0-9"

Here is the winner, Magnus Holm‘s solution (The name sounds familiar? Right, the previous example was from him too – he understands the power of redo for sure :-) ).

(0..9).map{rand(?z).chr[/[^_\W]/]||redo}*""

The above solution might not be instantly crystal clear – so let’s break it down a bit:

  1. rand(?z) is equivalent to rand(122) (but shaves off a character!)
  2. rand(?z).chr turns the random number into a string (equivalent to its ASCII code, i.e. 65.chr == “A” etc)
  3. the regexp /[^_\W]/ means the negation of ‘everything but underscore or non-word character (digits and numbers). To put it another way, accept word characters but not an underscore. (\w includes underscore, so we need to get rid of that)
  4. rand(?z).chr[/[^_\W]/] acts as an identity function for letters and digits, but returns nil for everything else. So “a”[/[^_\W]/] == “a” but “@”[/[^_\W]/] == nil. “_”[/[^_\W]/] also returns nil.
  5. And now comes the redo trick: rand(?z).chr[/[^_\W]/] || redo will restart the block until a random character/letter is not found! w00t!

While one might argue that golfing examples are contrived by definition, I don’t always agree: the [/[^_\W]/] is a neat trick to get numbers/letters from a string only, as well as the use of redo is valid and non-contrived.

Conclusion

As demonstrated above, redo can be used in a variety of situations to produce an elegant solution that would look much more cumbersome without it. Of course all the usual stuff applies: don’t overuse, abuse etc. etc.

If you have any other interesting cases for redo, leave a comment – I’d love to see some more :-)