How do I temporarily change the require path in Ruby ($:)?
I'm doing some trickery with a bunch of Rake tasks for a complex project, gradually refactoring away some of the complexity in chunks at a time. This has exposed the bizarre web of dependencies left behind by the previous project maintainer.
What I'd like to be able to do is to add a specific path in the project to require
's list of paths to be searched, aka $:
. However, I only want that path to be searched 开发者_运维问答in the context of one particular method. Right now I'm doing something like this:
def foo()
# Look up old paths, add new special path.
paths = $:
$: << special_path
# Do work ...
bar()
baz()
quux()
# Reset.
$:.clear
$: << paths
end
def bar()
require '...' # If called from within foo(), will also search special_path.
...
end
This is clearly a monstrous hack. Is there a better way?
Since $:
is an Array, you have to be careful about what you are doing. You need to take a copy (via dup
) and replace
it later. It' simpler to simply remove what you have added, though:
def foo
$: << special_path
# Do work ...
bar()
ensure
# Reset.
$:.delete(special_path)
end
Without more info, it's difficult to know if there is a better way.
require
is actually a method, it's Kernel#require
(which calls rb_require_safe
) so you could at least perform your hackery in a monkey-patched version. If you like that kind of thing.
- Alias the orignal require out of the way
- If passed an absolute path, call the original require method
- Else iterate over load path by creating an absolute path and calling the original require method.
Just for fun I had a quick bash at that, prototype is below. This isn't fully tested, I haven't checked the semantics of rb_require_safe
, and you probably would also need to look at #load
and #include
for completeness -- and this remains a monkey-patch of the Kernel
module. It's perhaps not entirely monstrous, but it's certainly a hack. Your call if it's better or worse than messing with the global $:
variable.
module Kernel
alias original_require require
# Just like standard require but takes an
# optional second argument (a string or an
# array of strings) for additional directories
# to search.
def require(file, more_dirs=[])
if file =~ /^\// # absolute path
original_require(file)
else
($: + [ more_dirs ].flatten).each do |dir|
path = File.join(dir, file)
begin
return original_require(path)
rescue LoadError
end
end
raise LoadError,
"no such file to load -- #{file}"
end
end
end
Examples:
require 'mymod'
require 'mymod', '/home/me/lib'
require 'mymod', [ '/home/me/lib', '/home/you/lib' ]
精彩评论