Ruby:require vs require_relative - 解决在 Ruby <1.9.2 和 >=1.9.2 中运行的最佳实践

154
哪种方法是在Ruby中要求相对文件并且要在1.8.x和>=1.9.2中都能工作的最佳实践?
我看到几个选项:
- 只需做$LOAD_PATH << '.'并忘记一切。 - 做$LOAD_PATH << File.dirname(__FILE__)。 - require './path/to/file'。 - 检查RUBY_VERSION< 1.9.2,然后将require_relative定义为require,在以后需要使用require_relative的地方使用它。 - 检查是否已经存在require_relative,如果是,则尝试像前一种情况一样继续进行。 - 使用奇怪的构造,例如eval '__FILE__'eval 'File.dirname(__FILE__) + "/relative/path/to/file"' \_\_FILE\_\_
require File.join(File.dirname(__FILE__), 'path/to/file')
- 不幸的是它们似乎不能在Ruby 1.9中完全工作,因为例如:
<code>$ cat caller.rb
require File.join(File.dirname(__FILE__), 'path/to/file')
$ cat path/to/file.rb
puts 'Some testing'
$ ruby caller
Some testing
$ pwd
/tmp
$ ruby /tmp/caller
Some testing
$ ruby tmp/caller
tmp/caller.rb:1:in 'require': no such file to load -- tmp/path/to/file (LoadError)
    from tmp/caller.rb:1:in '<main>'</code>
即使更奇怪的结构:
require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
看起来可行,但很奇怪,而且不太好看。 使用backports gem-它有点重,需要rubygems基础设施,并包含大量其他解决方法,而我只是想使用相对文件使require工作。 在StackOverflow上有一个密切相关的问题,提供了更多示例,但没有给出明确的答案-哪种方法是最佳实践。 是否有任何像样的、被所有人接受的通用解决方案,能让我的应用程序在Ruby <1.9.2和>=1.9.2上运行?

更新

澄清:我不仅想要像“你可以做X”这样的答案-事实上,我已经在问题中提到了大部分选择。我想要的是理由,即它为什么是最佳实践,它的优缺点以及为什么应该选择它而不是其他方法。

3
嗨,我是新来的。有人能从头开始解释一下requirerequire_relative之间的区别吗? - Colonel Panic
3
在旧版的Ruby 1.8中,如果你运行文件a.rb并希望解释器读取和解析当前目录(通常与a.rb相同)中的文件b.rb内容,你只需写require 'b'即可,因为默认搜索路径包括当前目录。在较新的Ruby 1.9中,你需要写require_relative 'b'来实现这一点,因为require 'b'只会在标准库路径中搜索。这种情况可能会破坏向前和向后兼容性,特别是对于不需要正确安装(例如安装脚本本身)的简单脚本。 - GreyCat
你现在可以仅使用backports来进行require_relative操作,详见我的回答... - Marc-André Lafortune
11个回答

64
这个问题的解决方法已经被添加到“aws”宝石中,所以我想分享一下,因为这篇文章给了我启示。 https://github.com/appoxy/aws/blob/master/lib/awsbase/require_relative.rb
unless Kernel.respond_to?(:require_relative)
  module Kernel
    def require_relative(path)
      require File.join(File.dirname(caller[0]), path.to_str)
    end
  end
end

这使得你可以在 Ruby 1.8 和 1.9.1 中像 Ruby 1.9.2 一样使用 require_relative


3
您如何导入 require_relative.rb 文件?您需要先使用 require 命令导入 require_relative.rb 文件,然后再使用 require_relative 导入其他需要的文件。或者我漏掉了什么吗? - ethicalhack3r
7
require_relative 函数是包含在 Ruby 核心库的扩展项目中的,可以在这里找到:http://www.rubyforge.org/projects/extensions您可以使用 gem install extensions 命令安装它们。然后在您的代码中,在 require_relative 之前添加以下行:require 'extensions/all' (引自 Aurril 的帖子这里 - thegreendroid
@ethicalhack3r 只需将该代码复制并粘贴到您的 Ruby 脚本顶部,或者如果在 Rails 中,请将其放置在顶部的 environment.rb 或其他位置。 - Travis Reeder

46

在跳转到1.9.2之前,我使用以下方法进行相对要求:

require File.expand_path('../relative/path', __FILE__)
第一次看到这种写法可能会有点奇怪,因为它似乎在开头多了一个“..”。原因是expand_path将展开一个相对于第二个参数的路径,并且第二个参数将被解释为目录。显然__FILE__不是目录,但这并不重要,因为expand_path不在意文件是否存在,它只会应用一些规则来展开诸如...~之类的内容。如果你能克服最初的“等一下,这里多了一个..?”的想法,我认为上面的代码行非常好用。
假设__FILE__/absolute/path/to/file.rb,那么expand_path将构造字符串/absolute/path/to/file.rb/../relative/path,然后应用一个规则,该规则指示..应该删除其前面的路径组件(在本例中为file.rb),返回/absolute/path/to/relative/path
这是否是最佳实践?这取决于你所指的内容,但似乎在Rails代码库中经常出现,所以我认为这至少是一个常见的惯用语。

1
我也经常看到这种情况。虽然不太美观,但似乎运行良好。 - yfeldblum
12
稍微改进一下:需要使用 File.expand_path('relative/path', File.dirname(FILE)) 语句来获取相对路径的绝对路径。 - Yannick Wurm
1
我不认为这更干净,只是更长。它们两个都丑爆了,当在两个糟糕的选项之间选择时,我更喜欢需要打字更少的那个。 - Theo
6
似乎使用File.expand_path('../relpath.x', File.dirname(FILE))是更好的惯用法,尽管它更冗长。依赖于文件路径被解释为带有额外不存在目录的目录路径这一破碎功能可能会在该功能被修复时/如果出现问题。 - jpgeek
1
@Theo,你能举个例子吗?在最明显的情况下,例如cd path/to/a/file/../..是不正确的。 - Adam Spiers
显示剩余2条评论

6

在1.8版本中,Pickaxe有一个相关片段,如下:

def require_relative(relative_feature)
  c = caller.first
  fail "Can't parse #{c}" unless c.rindex(/:\d+(:in `.*')?$/)
  file = $`
  if /\A\((.*)\)/ =~ file # eval, etc.
    raise LoadError, "require_relative is called in #{$1}"
  end
  absolute = File.expand_path(relative_feature, File.dirname(file))
  require absolute
end

它基本上只是使用了Theo的答案,但是你仍然可以使用require_relative

如何检查此代码片段是否已正确激活?是使用$RUBY_VERSION还是直接检查是否存在require_relative来进行判断? - GreyCat
1
始终使用鸭子类型,检查是否定义了 require_relative - Theo
@Theo @GreyCat 是的,我会检查是否需要。我只是把这个片段放在这里供大家展示。就个人而言,我通常会使用Greg的答案,我只是因为有人提到它而没有自己拥有它而发布这篇文章。 - Paul Hoffer

6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

这不是一个好的安全习惯:为什么要暴露你的整个目录?

require './path/to/file'

如果 RUBY_VERSION < 1.9.2,则此方法无效。

use weird constructions such as

require File.join(File.dirname(__FILE__), 'path/to/file')

Even weirder construction:

require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')

Use backports gem - it's kind of heavy, it requires rubygems infrastructure and includes tons of other workarounds, while I just want require to work with relative files.

您已经回答了为什么这些选项不是最佳选项。

检查 RUBY_VERSION 是否小于 1.9.2,然后将 require_relative 定义为 require,在需要的地方使用 require_relative

检查 require_relative 是否已存在,如果已存在,则尝试按照前一个情况继续执行

这可能会起作用,但有一种更安全和更快速的方法来处理 LoadError 异常:

begin
  # require statements for 1.9.2 and above, such as:
  require "./path/to/file"
  # or
  require_local "path/to/file"
rescue LoadError
  # require statements other versions:
  require "path/to/file"
end

5

我喜欢使用rbx-require-relative gem (源代码)。它最初是为Rubinius编写的,但也支持MRI 1.8.7,并且在1.9.2中什么都不做。要求一个gem很简单,我不必把代码片段扔到我的项目中。

将其添加到您的Gemfile中:

gem "rbx-require-relative"

在你使用require_relative之前,需要先使用require 'require_relative'

例如,我的一个测试文件看起来像这样:

require 'rubygems'
require 'bundler/setup'
require 'minitest/autorun'
require 'require_relative'
require_relative '../lib/foo'

在我看来,这是最干净的解决方案,而且这个宝石不像backports那样沉重,与任何其他解决方案相比都更加简洁明了。


4

backports gem 现在允许单独加载后向兼容功能。

你可以很简单地这样做:

require 'backports/1.9.1/kernel/require_relative'
# => Now require_relative works for all versions of Ruby

这个require不会影响较新的版本,也不会更新任何其他内置方法。


3
另一个选项是告诉解释器要搜索哪些路径。
ruby -I /path/to/my/project caller.rb

3

我没看到使用__FILE__解决方案时指出的一个问题是,它们在符号链接方面存在问题。例如,假设我有:

~/Projects/MyProject/foo.rb
~/Projects/MyProject/lib/someinclude.rb

主脚本、入口点和应用是foo.rb。这个文件链接到~/Scripts/foo,位于我的$PATH中。当我执行'foo'时,这个require语句就会出错:
require File.join(File.dirname(__FILE__), "lib/someinclude")

因为 __FILE__ 是 ~/Scripts/foo,所以上面的 require 语句会查找 ~/Scripts/foo/lib/someinclude.rb,但显然该文件不存在。解决方法很简单。如果 __FILE__ 是一个符号链接,则需要对其进行解引用。Pathname#realpath 将帮助我们解决这个问题:

require "pathname"
require File.join(File.dirname(Pathname.new(__FILE__).realpath), "lib/someinclude")

2
如果您正在构建一个 gem,您不希望污染加载路径。
但是,在独立应用程序的情况下,只需将当前目录添加到加载路径中非常方便,就像在前两个示例中所做的那样。
我的投票将投给列表中的第一项。
我很想看到一些关于 Ruby 最佳实践的文献。

1
回复:"我想看看一些扎实的 Ruby 最佳实践文献。" 您可以下载 Gregory Brown 的 《Ruby 最佳实践》(http://blog.rubybestpractices.com/posts/gregory/022-rbp-now-open.html)。您也可以查看 Rails 最佳实践网站 - Michael Stalker

1
如果不存在(即在1.8以下),我会定义自己的relative_require,然后在所有地方使用相同的语法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接