Top-down Ruby Scripts: A simple way to organize simple, single-files scripts that may be run.

posted 2012-Mar-23

I write a lot of one-off scripts in Ruby; little self-contained programs that do their work, transforming data or files or whatnot.

When written well, these files create special classes or methods to do their work. And sometimes these classes or methods are useful outside the script, so I occasionally want to be able to require the file in another script without having the code run. Thus, we have the common idiom in Ruby of having a file that defines some code but only does its work if the file is being run and not just required:

#!/usr/bin/env ruby

# …define lots of classes and methods here…

if __FILE__==$0 
  # actually do the work of running
end

I like the functionality of this idiom, but not the form. If you’re tracing execution of the script, the very first thing it does (after defining classes and methods) is at the bottom of the file…which then calls up to code in the top of the file.

I like my scripts to proceed from top to bottom. Declare some constants, require some libraries, and then start running some high-level code. Methods called by this high level code come lower down in the file. Methods that they call are even lower. And so on. This way the first thing you see when you open a script is a high-level summary of what it does.

Thus, here’s how I personally write my scripts:

#!/usr/bin/env ruby
#encoding: utf-8

SOMECONST = "important user-configurable value"

require 'nokogiri' # and other gems that the script uses

def run!
  # Just a few high-level calls with descriptive names
  data_files = find_data_files
  data_files.each{ |file| process_data(file) }
  cleanup
end

# define the high-level methods or classes here,
# with more menial helpes relegated lower down.

run! if __FILE__==$0 

By making everything a helper method—even the main work of the script—we can order them however we like. Further, we can require this script in another file (or in IRB) and call various methods as needed, or even call run! to kick the script off.

But really, if you’re going to make something that is designed to be used by other code, don’t declare top-level methods like this; wrap them all in a module or class or something.

net.mind details contact résumé other
Phrogz.net