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