将目录添加到$LOAD_PATH(Ruby)

103

我看到有两种常用的方法将当前正在执行的文件所在目录添加到$LOAD_PATH(或$ :)中。如果你没有使用gem,这样做的优点是显而易见的。其中一种明显比另一种更冗长,但是否有理由选择其中一种呢?

第一种冗长的方法(可能有些过度):

$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))

更简单,快速且不太讲究的方法:

$:.unshift File.dirname(__FILE__)

有什么理由选择其中一个而不是另一个吗?


2
一个比冗长版本稍微简洁一些的代码是:File.expand_path(File.dirname(__FILE__)).tap {|pwd| $LOAD_PATH.unshift(pwd) unless $LOAD_PATH.include?(pwd)} - Nathan Long
“unless”子句怎么样?这两个怎么等价呢? - inger
作为一个尝试理解如何使用这个的人,它非常晦涩难懂。我看不到示例中目录名字从哪里来。如果有人能把这个讲清楚就好了。 - SlySherZ
2
使用__dir__(自Ruby 2.0起)可以使这些更加简洁。 - Nathan Long
8个回答

160

Ruby的加载路径通常被写成$:,但仅仅因为它很短,并不意味着它更好。如果你更注重清晰而非聪明,或者仅仅为了简洁而感到不适,那么你不必像其他人一样这样做。

$LOAD_PATH

... and say goodbye to ...

# I don't quite understand what this is doing...
$:

31
搜索只包含符号(如“$:”)的字符串会更加困难。 - DSimon

55

我认为选择$:.unshift File.dirname(__FILE__)而不是$LOAD_PATH,仅仅因为我在代码中看到过更多它的使用,而且它更加简短!


当我刚开始学习 Ruby 时,我显然认为 $LOAD_PATH 更好。但是一旦你从初学者阶段毕业,我只会在尝试使我的代码更易于初学者阅读时使用 $LOAD_PATH。这是一个权衡。它取决于代码的“公开程度”,只要每个变量的内存使用情况相同,我认为它基本上就是这样。 - boulder_ruby
11
这取决于你的项目所遵循的样式指南。流行的Ruby Style Guide建议“避免使用Perl风格的特殊变量(如$:,$;等)。它们相当晦涩,除了一行脚本之外不建议在其他地方使用。” - bobmagoo
喜欢详细而不是简短的表达方式 :) - undefined

28

我不太喜欢“草率”的方式。 对于Ruby的新手来说,他们会思考$:.是什么。

我认为这样更明显。

libdir = File.dirname(__FILE__)
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)

或者如果我关心完整路径...

libdir = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)

更新 2009/09/10

最近我一直在做以下工作:

$:.unshift(File.expand_path(File.dirname(__FILE__))) unless
    $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

在浏览GitHub时,我在许多不同的Ruby项目中看到了它。

似乎是惯例吗?


@LukeAntins,这真的很棒,但我应该在应用程序中的哪里“引导”load_path? - gaussblurinc
1
@gaussblurinc 在您的 lib/application "附近" 的某个地方,但这真的取决于情况。如果您有一个始终相对于您的 code 文件的 bin 文件,并且它仅由 bin 文件运行... 那么在 bin 中进行引导。如果您有一个库,则在您的库代码顶部进行引导,例如在 lib/code.rb 中,以获得 lib/code/ 下的所有内容。希望这个讲话能有所帮助! - Luke Antins
1
RuboCop 提醒我可以使用 __dir__ 来获取当前文件所在目录的路径。 - Raphael

9

在使用 Rspec 时,我发现这是添加相对路径的最佳方式。我认为它足够详细且仍然是一个漂亮的一行代码。

$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))

8
如果您在Rails项目中输入script/console并输入$:,您将得到一个包括加载Ruby所需所有目录的数组。这个小练习的重点是$:是一个数组。既然如此,您可以使用unshift方法或<<运算符将其他目录添加到它之前执行它的函数。正如您在声明中暗示的那样,$:$LOAD_PATH是相同的。
像您提到的快速且不太规范的方法的劣势是:如果您已经在启动路径中拥有该目录,它将重复出现。
例如:
我创建了一个名为todo的插件。我的目录结构如下:
/---vendor | |---/plugins | |---/todo | |---/lib | |---/app | |---/models |---/controllers | |---/rails | |---init.rb
在init.rb文件中,我输入了以下代码:
## In vendor/plugins/todo/rails/init.rb
    %w{ models controllers models }.each do |dir|
      path = File.expand_path(File.join(File.dirname(__FILE__), '../lib', 'app', dir))
      $LOAD_PATH << path
      ActiveSupport::Dependencies.load_paths << path
      ActiveSupport::Dependencies.load_once_paths.delete(path)
    end 

请注意我是如何告诉代码块在字符串'models'、'controllers'和重复的'models'中执行操作的。 (FYI,%w{ ... }只是另一种告诉Ruby保存字符串数组的方法)。当我运行script/console时,我输入以下内容:
>> puts $:

我使用这种方式使得字符串的内容更容易阅读。输出结果如下:

...
...
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/models
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/controllers
./Users/Me/mySites/myRailsApp/vendor/plugins/todo/lib/app/models

可以看到,虽然这个例子很简单,但如果你不小心地采用快速且不规范的方法,可能会导致路径重复。而长一点的方式则会检查是否有重复的路径,并确保它们不会发生。

如果你是一个经验丰富的Rails程序员,那么你可能已经非常清楚自己在做什么,并且不会犯重复路径的错误。如果你是一个新手,我建议你采用较长的方式,直到你真正理解自己在做什么。


你的回复非常有帮助,而且解释得很清楚。建议进行编辑:方法 load_pathsload_once_paths.delete 已经过时。建议更新涉及到它们的代码行,更新为以下内容:ActiveSupport::Dependencies.autoload_paths << path ActiveSupport::Dependencies.autoload_once_paths.delete(path) - Uzzar

0

我的看法是:我喜欢使用$LOAD_PATH而不是$:。我已经老了……我学过92,000种语言,很难跟上所有的习惯和习语。

我开始厌恶命名空间污染。

最后,当我处理路径时,我总是先删除,然后再附加或前置——这取决于我希望搜索如何进行。因此,我会这样做:

1.times do
  models_dir = "#{File.expand_path(File.dirname(__FILE__))}/models"
  $LOAD_PATH.delete(models_dir)
  $LOAD_PATH.unshift(models_dir)
end

0

似乎不再存在。 - sekmo

-2

我知道这个问题被提出已经很久了,但是我有一个额外的答案想要分享。

我有几个由另一个程序员在多年内开发的Ruby应用程序,它们在不同的应用程序中重复使用相同的类,尽管它们可能访问相同的数据库。由于这违反了DRY原则,我决定创建一个类库,供所有Ruby应用程序共享。我本可以把它放在主Ruby库中,但那样会隐藏常规代码中的自定义代码库,这是我不想做的。

我遇到了一个问题,即我已经定义了一个名为“profile.rb”的名称冲突,而我正在使用一个类。这个冲突直到我尝试创建通用代码库才成为问题。通常,Ruby首先搜索应用程序位置,然后转到$LOAD_PATH位置。

application_controller.rb找不到我创建的类,并在原始定义上抛出错误,因为它不是一个类。由于我从应用程序的app/models部分中删除了类定义,Ruby无法在那里找到它,于是去Ruby路径中寻找它。

因此,我修改了$LOAD_PATH变量,将其包含到我正在使用的库目录的路径中。这可以在初始化时的environment.rb文件中完成。

即使将新目录添加到搜索路径中,Ruby仍然会抛出错误,因为它优先选择系统定义的文件。$LOAD_PATH变量中的搜索路径优先搜索Ruby路径。
所以,我需要改变搜索顺序,让Ruby在搜索内置库之前先找到我的公共库中的类。
这段代码在environment.rb文件中实现了这一点:
Rails::Initializer.run do |config|

* * * * *

path = []
path.concat($LOAD_PATH)
$LOAD_PATH.clear
$LOAD_PATH << 'C:\web\common\lib'
$LOAD_PATH << 'C:\web\common'
$LOAD_PATH.concat(path)

* * * * *

end

我认为在这个级别上你不能使用之前提供的任何高级编码结构,但如果你想在应用程序初始化时设置某些东西,那么它完全可以正常工作。在将原始$LOAD_PATH变量添加回新变量时,必须保持原始顺序,否则一些主要的Ruby类会丢失。

在application_controller.rb文件中,我只是简单地使用了一个

require 'profile'
require 'etc' #etc

这将为整个应用程序加载自定义库文件,即我不必在每个控制器中使用 require 命令。

对我来说,这是我正在寻找的解决方案,我想将其添加到此答案中以传递信息。


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