I'd like to know how people people managing their file dependencies
with projects with several directories. Described below is the
problem, and a demo of a possible rudimentary solution for which I'd
like feedback/discussion. (I've searched and can't find any notes for
handling requires elegantly. It could be that I'm searching in the
wrong place ... please point me in the right direction if yes)
I've been working on a small project with several directories. Using
the $:.unshift File.join( ( File.expand_path( File.dirname( __FILE__ )
) ), '..', '..', 'blah' ) trick gets pretty boring to type, doesn't do
much for the code readability, and doesn't help when files get moved
around.
Side note: As a relic of my C++ interest, I also like my requires to be
self-contained so my files are independent (e.g., if A uses B and C,
I'll require B and C in A, even if B requires C ... that way, if the
require 'C' is removed from B, A will still work).
Here's the quick demo of one possible solution for handling requires
for a project with multiple subdirectories:
in root:
···
--------
** File add_dirs_to_search_path.rb:
require 'find'
Find.find( File.expand_path( File.dirname( __FILE__ ) ) ) do |path|
if FileTest.directory?(path)
$:.unshift path
end
end
in root/A:
----------
** File a_thing.rb:
module A
class AThing; def go() "A go!"; end; end
end
in root/B:
----------
** File a_client.rb:
require 'a_thing'
module B
class BThing; def go() "B go: #{A::AThing.new.go}"; end; end
end
Back in root:
-------------
** File main.rb:
$:.unshift File.expand_path( File.dirname( __FILE__ ))
require 'add_dirs_to_search_path'
require 'a_client'
b = B::BThing.new
puts b.go
Result when called from root:
$ ruby main.rb
B go: A go!
Notes, advantages, drawbacks:
- Module names mimic the directory structure (per the code conventions
at RWiki)
- B only has to use "require 'a_thing', since a_thing's directory was
pushed into the include search path
- Any code that uses B must now have specified the search path to
a_thing.rb somehow, either using the -I command-line directive, an
implicit require (requiring some code that requires A), or by requiring
add_dirs_to_search_path.rb
- main has to have "$:.unshift File.expand_path( File.dirname( __FILE__
)); require 'add_dirs_to_search_path'". So would any test suite, etc.
- Directory changes are easily handled. You can move A::AThing to a
different directory (note that this change should probably be
accompanied by a change in module name, per RWiki code conventions)
- To disambiguate the desired file, include the directory. For
example, if root/C also contains a file a_thing.rb, and you wanted to
use *that* file in main, you'd change the require in main to "require
File.join( 'C', 'a_thing.rb' )".
- The require gets mixed up if different folders with the same name
contain files with the same name. For example, if there's another file
A/C/a_thing.rb, and the module definition matches the directory
structure (module A; module C; class AThing; ...), the require *can*
bomb since the wrong file might be included.
- in add_dirs_to_search_path.rb, I'm dynamically finding the files, but
you could also hardcode them:
dirs = [ 'a',
'a|suba',
'b',
'b|subb',
'c' ]
root = File.expand_path( File.dirname( __FILE__ ) )
dirs.each do |d|
$:.unshift "#{root}|#{dirs}".gsub( '|', File::SEPARATOR )
end
This whole idea might be kind of silly ... it might be better to be
explicit when including files (like you have to do in Java ... "import
com.x.my.utilities.rock.Thingy"), and to always fully specify modules
whenever you're referring to a class.
Sorry for the long post - but I'd like to hear any good strategies for
managing requires. Thanks,
Jeff