29 April 2008
An Optional Require for Ruby

In the fine balance between code organization and keeping the system quiet, I like to err on the side of silence. I just don’t like warnings and noise coming from the compiler and runtime, and will sometimes code around the squeaky parts of a system so I don’t have to listen to the racket. Despite the messiness of my physical reality, I like to keep my virtual reality well-organized and clean. That goes for my code too - I don’t like writing Goldbergian code unless it’s for play. I like to put the right code in the right place.

I write most of my Java code in Eclipse these days and there are little noisy warnings that seem to pop up everywhere. While they can be suppressed or filtered, I kind of hate it when I have to refactor correct code and move things around because of the judgement of a coding tool. Happily, I don’t have to in Ruby. First, it doesn’t have a compile time and unit testing removes the stray vibrations from the code, so it’s almost always nice and quiet. And second, the concept of where things go is never tainted by file boundaries - code for any class can go into any file and Ruby sorts it all out.

Yes, everything was nice and clean… until yesterday.

I was writing some code to open html documents, keeping everything organized together so the functionality was all in place. The code was intended to open the html in the user’s selected browser on any platform so I could call one high-level method and have it just work. To do this on Windows I needed the win32/registry feature. So I added it with a require and everything was fine. I was happy. Later I pulled the code onto my iMac and ran it. LoadError.

I was aghast. My Ruby was befouled. Of course, Apple has no need for Windows Registry access, and so was missing the feature I had required. Fine. I could install the gem anyway and move forward. But then I thought, this is code I want to use in production - customers would be using this and there is no need for them to load that gem if they were on Apple or Linux.

So I was faced with two evils. Pull in the gem or code around it. I decided to code around it. I then had to decide whether to break up the module and only include the windows part if needed, or keep everything together so the logical functionality was colocated. I decided to keep everything together. This now meant that I needed some conditions around my require.

require 'win32/registry' if Config::CONFIG["target_vendor"] == "pc"

That’s fine - everything was back on track.

I did get to thinking that I really was working too hard, and that I’d end up repeating myself again somewhere down the road. I wanted something more general, that would give me a pass if a LoadError occurred, and handle the missing piece later somehow. I decided to create an optional require.

class Object
  def optional_require feature
    begin
      require feature
    rescue LoadError
    end
  end
end

Now if I used it and got a LoadError, everything would just move on.

optional_require 'win32/registry'

This removed the guts of the decision from the actual require, and expected the require to fail on the platforms that didn’t need it. It also left the door open for writing a check_configuration method that would make sure that the user’s feature set was complete when an application starts, which to me is just good practice. I was happy enough with this solution to place it into the eymiha rubygem, available at rubygems.org.

So, less noise and more organization. Just what I was looking for.