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.


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 < 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, ‘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_PASSWORD).post(fortune)

# save that this fortune was used used[fortune.sha1] = YAML::save_file(FILE_USED, used)

# set time of next tweet next_tweet_at = + 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.