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.