Thursday, February 22, 2007

Ruby Lib Path Manipulations

Mucking around with the library search path in ruby isn't exactly the hardest thing in the world. It's just a matter of fiddling around with a horrible looking perlish global variable and using __FILE__ to munge the right path together. Again, nothing too difficult but it's annoying and ugly—totally out of place next to all that beautiful ruby code.

For the most part, this isn't much of a problem for libraries. In fact, you don't normally want to hard code paths anyway. Typically a flag is passed to the ruby interpreter:

$ ruby -Isome/path foo.rb

However, in some cases you still need to change the path in the code. Likely the most common place this shows up is within test/spec files. Does this look familiar?

# some_cool_spec.rb
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')

require 'lib_under_test'

context '...' do
# snip
end

It’s so ugly, right? To me it's quite the eyesore so I wrote the following really simple library (rubylib.rb) to fix it up a bit:

module LibCommands
def lib(*path_segs)
  $:.unshift File.join(*path_segs)
end

def lib_rel(*path_segs)
  file = caller.first.split(':').first
  lib File.dirname(file), *path_segs
end
end

Object.send :include, LibCommands

Now when you want to add something to the load path, you can just use the lib method:

lib 'path', 'to', 'library'

It's even better when dealing with relative paths. lib_rel specifies the directory to add relative to the file it's called from—rather than the current working directory, which is probably different. That makes the above rspec:

# some_cool_spec.rb
lib_rel '..', 'lib'

require ‘lib_under_test’

context '...' do # snip end

Nice, eh? Sure, but you still need to require rubylib.rb first. You can fix that up with a few little tricks used by rubygems. First, create aubylib.rb that just requires rubylib.rb. That way it looks nicer when you add it in as a flag to ruby.

$ ruby -rubylib foo.rb

The next level of laziness is to add it you your RUBYOPT. With the environment variable set the two commands are available without any explicit requires:

$ export RUBYOPT="${RUBYOPT} -rubylib"
$ ruby foo.rb

1 comments:

Jim B said...

I get you. Nice post.

Is there a Java-esque way of "faking" an import, or would I be missing the point, as the lack of hierarchical packaging in Ruby is twisting my melons.