Bauble, Bauble...
Posted by Uncle Bob on 07/20/2008
In Ruby, I hate require statements that look like this:
require File.dirname(__FILE__)+"myComponent/component.rb"
So I decided to do something about it.
This all started when my Son, Micah, told me about his Limelight project. Limelight is a jruby/swing GUI framework. If you want to build a fancy GUI in Ruby, consider this tool.
I have neither the time nor inclination to write a framework like this; but my curiosity was piqued. So in order to see what it was like to do Swing in JRuby I spent a few hours cobbling together an implementation of Langton’s Ant. This turned out to be quite simple.
The result, however, was a mess. There was swing code mixed up with “ant” code, in the classic GUI/Business-rule goulash that we “clean-coders” hate so much. Despite the fact that this was throw-away code, I could not leave it in that state – the moral outrage was just too great. So I spent some more time separating the program into two modules.
The first module knew all about Langton’s ant, but nothing about Swing. The second module was a tiny framework for implementing cellular automata in Swing. (Here are all the files).
I was quite happy with the separation, but did not like the horrible require statements that I had to use. The cellular_automaton component had two classes, in two separate files. In order to get the require right, I had to either use absolute directory paths, or the horrible File.dirname(__FILE__)... structure.
What I wanted was for cellular_automaton to behave like a gem. But I didn’t want to make it into a gem. Gem’s are kind of “heavy” for a dumb little thing like “cellular_automaton”.
So I created a module named “Bauble” which gave me some gem-like behaviors. Here it is:
module Bauble
def self.use(bauble)
bauble_name = File.basename(bauble)
ensure_in_path "#{bauble}/lib"
require bauble_name
end
def self.ensure_in_path(path)
$LOAD_PATH << path unless $LOAD_PATH.include? path
end
end
This is no great shakes, but it solved my problem. Now, in my langton’s ant program all I need to do is this:
require 'bauble'
Bauble.use('../cellular_automaton')
All the ugly requires are gone.
I’m thinking about turning Bauble into a rubyforge project, and making a publicly available gem out of it in order to give folks a standard way to avoid those horrible __FILE__ requires. I think there are several other utilities that could be placed in Bauble such as require_relative etc.
Anyway, what do you think?
Comments
Bil Kleb about 3 hours later:
If you used Hoe’s sow command, e.g.,
$ sow bauble
you may have a gem before you might expect.
$ sudo gem install hoe
Jim Weirich about 22 hours later:
I’ve never been a fan of the require/FILE idiom either. However, I also don’t like using paths like ”../cellular_automaton” either … mainly because (if I’ve read you code right), the location of the cellular_automaton directory is calculated based on your current directory. That’s fine for running in the project’s directory, but what if I want to run it from my home directory. IMHO, refs to ”../dir” do not belong in code.
Here’s what I do. I project is structured like this:
project_dir
lib
langton
cellular_automaton
ui
All requires would be of the form: require ‘langton/cellular_automation/whatever’. This only requires a -Ilib option on any manually typed ruby command (ie. all refs are relative to ‘lib’). The standard rake test task understands this and will add a -Ilib option automatially). This removes the need for any FILE relative requires.
And the truly lazy can put ‘lib’ into their RUBYLIB environment variable.
This scales nicely to installed libraries, which would either copy the ‘lib/*’ to a directory listed in $LOAD_PATH, or as a GEM which would add the projects ‘lib’ directory to $LOAD_PATH.
Micah Martin about 23 hours later:
Using Bauble.use instead of require would be a bit unorthodox. But you could get used to it.
I imagine you could monkey patch Kernel.require to do exactly what Baubleis doing. That’s a bit scary though.
I agree that require statements can get nutty. I blogged about Ruby require guidlines a while back. In a nut shell, I recommend usingFile.dirname(__FILE__) one time in your code base to set up a search path, and never use it again.
Unclebob 1 day later:
The problem I am trying to solve is when I have many different components that I want to import into my app and I don’t want to turn them all into gems.