Feature #19889
closedLet `Kernel.#require` search for files relative to the current working directory for non ./, ../ relative paths
Description
My understanding is that ./
and ../
in the given path argument are interpreted relative to:
(1)
- The current working directory (for
load
orrequire
) - The requiring file's path (for
require_relative
)
which shows a division of labor between the methods, and seems reasonable. However, when it comes to other relative paths (e.g., foo/bar.rb
), they are interpreted relative to:
(2)
- Paths in
$LOAD_PATH
(forrequire
) - Paths in
$LOAD_PATH
or the current working directory (forload
orrequire_relative
)
For example, given:
- File
some_path/foo/a.rb
- File
some_path/b.rb
with contentrequire "foo/a"
- Current directory at
some_path
,
running ruby b.rb
raises a LoadError
, but given:
- File
some_path/foo/a.rb
- File
some_path/b.rb
with contentrequire_relative "foo/a"
- Current directory at
some_path
,
running ruby b.rb
does not raise an error.
The search path in (2) for require
is a proper subset of that of load
and require_relative
. There is no division of labor here; there is only inconvenience for require
.
Furthermore, in (1), require
(as well as load
) is concerned with the current working directory while require_relative
is not, but in (2), the relation is reversed: require_relative
(as well as load
) is concerned with the current working directory while require
is not.
This situation is making the specification of require
versus require_relative
difficult to understand, as well as causing inconvenience.
Proposal: For non-./
-or-../
relative paths, I propose to let Kernel.#require
search relative to the current working directory if the file is not found relative to the paths in $LOAD_PATH
, so that the methods load
, require
, and require_relative
all work the same in this respect.
Updated by nobu (Nobuyoshi Nakada) about 1 year ago
- Status changed from Open to Feedback
sawa (Tsuyoshi Sawada) wrote:
My understanding is that
./
and../
in the given path argument are interpreted relative to:(1)
- The current working directory (for
load
orrequire
)- The requiring file's path (for
require_relative
)
The former is correct, the latter is wrong.
Always ./
means the current working directory, never the loading file's directory.
Proposal: For non-
./
-or-../
relative paths, I propose to letKernel.#require
search relative to the current working directory if the file is not found relative to the paths in$LOAD_PATH
, so that the methodsload
,require
, andrequire_relative
all work the same in this respect.
You can add "."
to $LOAD_PATH
, at your own risk.
Historically, $LOAD_PATH
had contained "."
if $SAFE
is 0 old days, but it was totally removed in 2009 for security reasons.
I don't think we will revise it.
Updated by rubyFeedback (robert heiler) about 1 year ago
sawa wrote:
This situation is making the specification of require versus require_relative
difficult to understand, as well as causing inconvenience.
I kind of stay(ed) with require and almost never use require_relative. So for me
at the least it is not really an issue. Other ruby devs use require_relative
fine in their gems and code. I guess it is a matter of preference and how you
want to work with extension-code you write in ruby. For me it seemed easier to
think of every extensions as a gem, at the least when possible, and there I
can do require 'name_of_the_gem/first_file_to_load.rb', which then may load
all the other .rb files.
As for ./ and ../ - I kind of try to ignore them completely, and when I get
a listing of a directory content or absolute path, I try to always store
directories, or rather name of directories, with a trailing '/'. My mind just
prefers to see a trailing '/' whenever I work with a directory.
In many of my projects, under base/base.rb, I tend to add a method such as
"def pwd" or "def return_pwd", that is basically Dir.pwd but also ensures
there is one trailing /. This may seem rather pointless, but I kind of like
to use it consistently, and not rely on Dir.pwd directly. I also try to use
File.absolute_path() when it is feasible, even if it may not be absolutely
necessary. It just seems easier for me to rely on such awkward patterns.
On the other hand I (almost) never modify $LOAD_PATH; I think I only do so
for irbrc or so; other than that I try to just let ruby handle $LOAD_PATH
for me. At the least most of the time.
sawa wrote:
Proposal: For non-./-or-../ relative paths, I propose to let Kernel.#require
search relative to the current working directory if the file is not found
relative to the paths in $LOAD_PATH, so that the methods load, require,
and require_relative all work the same in this respect.
I somewhat understand the thought process here, even more so as I myself try
to avoid require_relative. :) But I think, people can use just require() too,
or? For some of the ruby code I use locally, such as, a simple example, the file at strategeme/strategeme.rb, I have:
require_relative 'common.rb'
And this is then a local .rb file I use to power all the other ruby code
that is solely for local use. The rest I tend to publish freely available
on rubygems.org, where I don't need to use require_relative.
Interestingly some ruby gems seem to prefer require_relative, e. g.
webrick:
httpservlet.rb # require_relative 'httpservlet/filehandler'
In such cases I just use require directly rather than require_relative.
But functionality-wise, I think both achieve just about the same?
Updated by vo.x (Vit Ondruch) about 1 year ago
I think that the biggest issue is with require_relative
and expansion of the relative paths to absolute paths. What it should actually do IMHO is add the current directory, form which the relativeness is derived, to the $LOAD_PATH
and use the usual require
afterwards.
Updated by vo.x (Vit Ondruch) about 1 year ago
In short, this could be the implementation:
def require_relative(path)
$LOAD_PATH.unshift __dir__
require path
$LOAD_PATH.shift
end
Anything else breaks is harmful (talking about issues such as #16978 which is related IMHO).
Updated by vo.x (Vit Ondruch) about 1 year ago
Actually this could be even better and more in line with current implementation:
def require_relative(path)
original_load_path = $LOAD_PATH.dup
$LOAD_PATH.replace [__dir__]
require path
$LOAD_PATH.replace original_load_path
end