Auto-loading Ruby Code

11 Feb 2007

An incredibly useful technique when using Ruby is to auto-load at start-up a custom library written for exactly that purpose. This is easy to accomplish with a couple of environment variables, but I see very little discussion on the subject. Thus, I’ve written a nice summary of how to go about setting this up.

The secret to running code at start-up is RUBYOPT. You’ve probably seen this environment variable before if you’ve used RubyGems. It is recommended to set this to a value of rubygems. This is equivalent to always calling ruby as ruby -rubygems. The -r option requires a library, so this option essentially does a require 'ubygems' each time Ruby is started. (The odd name of ubygems was picked to look nice next to -r.)

We’re going to be doing something similar, but with our own library. I’ve traditionally put this file in ~/.ruby/lib/tpope.rb, but I will be using the more user-agnostic mine.rb for the purposes of this example.

The first thing we need to do is find the right place to set this environment variable, along with setting RUBYLIB to a path that holds our file. If you are using a bourne compatible shell, like bash, add this to your shell’s rc file (~/.bashrc for bash):

if [ -f "$HOME/.ruby/lib/mine.rb" ]; then
  RUBYLIB="$HOME/.ruby/lib"
  RUBYOPT="rmine"
  export RUBYLIB RUBYOPT
fi

For C shells, add the following to ~/.cshrc:

if ( -f "$HOME/.ruby/lib/mine.rb" ) then
  setenv RUBYLIB "$HOME/.ruby/lib"
  setenv RUBYOPT "rmine"
endif

If you are running under a GUI environment, you will want to make this variables known to the top level GUI as well. Both Windows and Mac OS X have interfaces to set environment variables. Some desktop environments running under X11 provide such an interface as well, or you can just add similar lines to ~/.xsession or ~/.xinitrc, depending on your setup.

So now that we have this configured, what can we add to this new file? Plenty of things. Let’s start by enhancing the $LOAD_PATH (also known as $:). My strategy here is to pop the current working directory off the end, add a few more directories, then restore it.

old_current = $LOAD_PATH.pop if $LOAD_PATH.last == '.'
%w(.ruby/lib ruby/lib .ruby ruby).each do |dir|
  $LOAD_PATH.unshift(File.expand_path("~/#{dir}"))
end
Dir[File.expand_path('~/ruby/*/lib')].each do |dir|
  $LOAD_PATH << dir
end
$LOAD_PATH << old_current if old_current
$LOAD_PATH.uniq!

I add a wide variety of directories here, feel free to adjust as you see fit. Note the addition of every directory matching ~/ruby/*/lib, which makes it easy to install new libraries in ~/ruby. This is something that would be tricky to do just by adjusting RUBYLIB.

Next, let’s load RubyGems. We can do this in such a way that it won’t fail on systems where it’s not installed.

begin
  require 'rubygems'
rescue LoadError
end

What else? How about always keeping good old Symbol#to_proc handy.

class Symbol
  def to_proc
    Proc.new { |*args| args.shift.__send__(self,*args) }
  end
end

One thing to be careful about is not letting your code grow dependent on this auto-loaded library. For example, if you add the above snippet and start doing pets.each(&:feed) all over the place, your code will break for other people without your library. I still define Symbol#to_proc because it’s handy for debug statements and quick scripts and it’s easy enough to avoid using where it’s important. But I avoid something more invasive like require 'active_support' because it’s harder to keep track of everything it provides. Use your own best judgement.