Twitter and Ruby’s Open Classes

For a few years I’ve been using weird, funny, outrageous, bizarre, or just offensive quotes as IM status messages. They used to appear at the bottom of this site, but the Jabber bot that fetched them has been offline for a while. I hooked them up to a Twitter account with a short Ruby script, and I wanted to talk about it a little.

 
#!/usr/bin/ruby
 
FILE_FORTUNE = '/path/to/.fortunerc'
FILE_USED = '/path/to/.used-fortunes'
TWITTER_USERNAME = 'username'
TWITTER_PASSWORD = 'seekrit password'
GAP = (2 * 60)..(26 * 60) # gap between tweets in minutes:
 
# A cron job runs the script every minute with the word 'cron' 
# as an argument. It exits immediately unless enough time has passed
exit if ARGV[0] == "cron" and Time.now < File.mtime(FILE_USED)
 
require 'rubygems'
require 'digest/sha1'
require 'twitter'
require 'yaml'
 
 
class Array
  def rand
    self[Kernel.rand(length)]
  end
end
 
class Range
  def rand
    Kernel.rand(last - first) + first
  end
end
 
class String
  def sha1
    Digest::SHA1.hexdigest(self)
  end
end
 
module YAML
  def self.save_file filename, obj
    File.open(filename, 'w') { |f| f.write(obj.to_yaml) }
  end
end
 
 
# load fortunes, loop to pick one randomly
fortune = nil
used = YAML::load_file(FILE_USED)
fortunes = open(FILE_FORTUNE).read.split("\n%\n")
loop do
  fortune = fortunes.rand
  break if fortune.length <= 140 and !used.include? fortune.sha1
end
 
# post it to Twitter
Twitter::Base.new(TWITTER_USERNAME, TWITTER_PASSWORD).post(fortune)
 
# save that this fortune was used
used[fortune.sha1] = Time.now
YAML::save_file(FILE_USED, used)
 
# set time of next tweet
next_tweet_at = Time.now + GAP.rand * 60
File.utime(next_tweet_at, next_tweet_at, FILE_USED)

A short, single-purpose script like this doesn’t cry out for seemingly-fancy techniques like adding to builtin classes. But when you get to the core of the script, it’s obvious. Instead of the noise of a procedural calls for randomness and generating sha1s, the intention is the implementation. The reusable bits of the code are already explicitly extracted, making them easier to reuse.

Now that I’ve been doing this sort of thing in Ruby for a while I keep wishing I could do it in Python. With open classes I can add to the Strings and Arrays that other code returns to mine. I can’t subclass objects I don’t instantiate, the best I could do is write a delegating wrapper class.

Want more? I'm not as good at forgetting to update @pushcx on Twitter.