header image

Archive for February, 2009

Advanced Twitter Queries with the Twitter Gem

Wednesday, February 25th, 2009

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:

  1. module Twitter
  2.   class Base
  3.     def all_entries(method, options = {})
  4.       all_entries = [] 
  5.       next_100 = self.send method, {:page => (current_page = 1)}.merge(options)
  6.       while (next_100.size != 0) do
  7.         all_entries << next_100
  8.         next_100 = self.send method, {:page => (current_page += 1)}.merge(options)
  9.       end 
  10.       all_entries.flatten
  11.     end
  12.   end
  13. 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):

  1. module Twitter
  2.   class Base
  3.     alias_method :throw_method_missing, :method_missing
  4.  
  5.     def method_missing(method_name, *args, &bloke)
  6.       if (method_name.to_s =~ /^all_.+/)
  7.         all_entries(method_name.to_s[/all_(.+)/,1], args[0] || {})
  8.       else
  9.         throw_method_missing(method_name, *args, &bloke)
  10.       end
  11.     end
  12.   end
  13. 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.

  1. class Array
  2.   def names
  3.     self.map{|u| u.screen_name}
  4.   end
  5. end
  6.  
  7. module Twitter
  8.   class Base
  9.     def not_following_back
  10.       all_friends.names - all_followers.names
  11.     end
  12.   end
  13. 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:

  1. require ‘rubygems’
  2. require ‘twitter’
  3.  
  4. connection =  Twitter::Base.new(‘yourname’, ‘yourpass’)
  5.  
  6. class Array
  7.   def names
  8.     self.map{|u| u.screen_name}
  9.   end
  10. end
  11.  
  12. module Twitter
  13.   class Base
  14.     alias_method :throw_method_missing, :method_missing
  15.  
  16.     def method_missing(method_name, *args, &bloke)
  17.       if (method_name.to_s =~ /^all_.+/)
  18.         all_entries(method_name.to_s[/all_(.+)/,1], args[0] || {})
  19.       else
  20.         throw_method_missing(method_name, *args, &bloke)
  21.       end
  22.     end
  23.  
  24.     def all_entries(method, options = {})
  25.       all_entries = [] 
  26.       next_100 = self.send method, {:page => (current_page = 1)}.merge(options)
  27.       while (next_100.size != 0) do
  28.         all_entries << next_100
  29.         next_100 = self.send method, {:page => (current_page += 1)}.merge(options)
  30.       end 
  31.       all_entries.flatten
  32.     end
  33.  
  34.     def not_following_back
  35.       all_friends.names - all_followers.names
  36.     end
  37.  
  38.   end 
  39. end 
  40.  
  41. 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

Friday, February 6th, 2009

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”):

  1. puts "Please enter the first word you can think of"
  2. words =%w(apple banana cherry)
  3. response = words.collect do |word|
  4.   print word + "> "
  5.   response = gets.chop
  6.   if response.size == 0
  7.     word.upcase!
  8.     redo
  9.   end
  10.   response
  11. 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:

  1. define_method(:acc) do |i, n, result|
  2.   if i == -1
  3.     result
  4.   else
  5.     i, n, result = i - 1, n + result, n
  6.     redo
  7.   end
  8. end
  9.  
  10. def fib(i)
  11.   acc(i, 1, 0)
  12. 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:

  1. def fib(i)
  2.   acc = lambda do |i, n, result|
  3.     if i == -1
  4.       result
  5.     else
  6.       i, n, result = i - 1, n + result, n
  7.       redo
  8.     end
  9.   end.call(i, 1, 0)
  10. 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 :-)).

  1. (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 :-)


Bad Behavior has blocked 685 access attempts in the last 7 days.