在Ruby中从一个目录中要求所有文件的最佳方法是什么?

382

如何在ruby中要求从一个目录中加载所有文件的最佳方法?

13个回答

500
如下所示:

怎么样:

Dir["/path/to/directory/*.rb"].each {|file| require file }

20
根据“Pickaxe”文档,.rb 扩展名是可选的。从技术上讲,它会改变含义:“require 'foo.rb'”会要求引入 foo.rb 文件,而“require 'foo'”可能会引入 foo.rb、foo.so 或 foo.dll 文件。 - Sam Stokes
29
不去掉文件扩展名有一个微妙的问题。如果代码的其他部分调用了 require 'foo',则 Ruby 会再次加载同一文件,这可能会导致错误。我添加了自己的答案,解释了这一点,并展示了如何去掉文件的扩展名。 - Pete Hodgson
5
@Pete,这还是真的吗?参见下面的Rene的评论 - Andres Riofrio
5
这可能很明显,但值得注意的是删除 .rb 扩展名也会影响目录中所有非 .rb 文件,这可能并不是期望的结果。 - user2398029
12
@PeteHodgson的建议是不准确的。Ruby的require并不会因为有或没有.rb扩展名而混淆。在MRI 1.8.7-p374、2.1.5和2.2.0版本测试过。这个谣言来自于Rails,在旧版本中展示了他所描述的行为,那时候“聪明”的自动加载可能仍然会出现这种情况。 - sheldonh
显示剩余4条评论

366
如果它是相对于执行需要的文件的目录(例如,您想加载lib目录中的所有文件):
Dir[File.dirname(__FILE__) + '/lib/*.rb'].each {|file| require file }

编辑:基于下面的评论,更新后的版本:

Dir[File.join(__dir__, 'lib', '*.rb')].each { |file| require file }

15
你可以像这样添加所有子目录:Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each {|file| require file } - jspooner
64
最好使用File.join而不是假设正斜杠或反斜杠:Dir[File.join(File.dirname(__FILE__), 'lib', '*.rb')].each {|file| require file } - Chris
@ChristopherPatuzzo 你真是救星啊。我一直搞不明白为什么文件从未被加载。"\current\file\path/lib/*.rb" - grasingerm
6
也需要使用 require_relative。 - maasha
30
如果您使用的是>= ruby 2.0,可以使用__dir__代替File.dirname(__FILE__) - Christian Bankester
5
你如何使用 require_relative 来引入一个目录中的所有文件? - David Moles

108

我需要包含所有的ActiveRecord模型,而require_all gem则完美地找出了所有的依赖并将它们引入。谢谢! - panupan
3
请注意,require_all库的循环依赖解决方案是为了解决你源代码中的一个问题:你的Ruby源文件没有引用它们的依赖项。这会关闭精细加载(scalpel loading)的可能性,使你只能进行全量加载或不加载。在小型库中这并不是一个问题,但你应该有意识地做出这个决定。 - sheldonh
7
用gem臃肿你的应用程序是没有意义的,你可以用一行代码来代替它们。这会增加应用程序的加载时间,并在长期内引入更多的错误。 - Pere Joan Martorell

53
Dir[File.dirname(__FILE__) + '/../lib/*.rb'].each do |file| 
  require File.basename(file, File.extname(file))
end
如果你不去掉文件扩展名,那么你可能会需要同一个文件两次(Ruby无法意识到"foo"和"foo.rb"是同一个文件)。重复需要同一个文件可能会导致虚假的警告(例如:"warning: already initialized constant")。

11
这是真的吗?文档说:如果一个特性已经在 $" 中出现过,那么它将不会被加载。文件名被转换为绝对路径,因此 "require 'a'; require './a'" 不会重复加载 a.rb。http://www.ruby-doc.org/core/classes/Kernel.html#M001418 - Derek
15
我的测试结果与 Derek 所说的一样:require "foo.rb"; require "foo";只会加载一次foo.rb - Rene Saarsoo
1
@PeteHodgson- 你能支持这个吗? - Yarin
5
Ruby的require命令不会因为是否存在.rb文件扩展名而混淆。在MRI 1.8.7-p374、2.1.5和2.2.0上进行了测试。这个谣言来自Rails,那里的“聪明”的自动加载行为在旧版本中呈现出所描述的行为(可能仍然如此)。 - sheldonh

49
Dir.glob(File.join('path', '**', '*.rb'), &method(:require))

或者,如果你想将文件的加载范围限定在特定的文件夹中:

Dir.glob(File.join('path', '{folder1,folder2}', '**', '*.rb'), &method(:require))
解释:

Dir.glob方法需要一个块作为参数。

method(:require)将返回require方法。

&method(:require)将把该方法转换为块。


2
这是一些漂亮的代码。我喜欢它没有可见的块。 - Nate Symer
1
Dir.glob(File.join(File.dirname(FILE), '{lib,addons}', 'subfolder', '**', '*.rb'), &method(:require)) 可以消除对平台(如'/'或'')的依赖。 运行良好。 谢谢。 - Ivan Black

36

最好的方式是将目录添加到加载路径中,然后require每个文件的基本名称。这是因为你希望避免意外地两次要求相同的文件--这通常不是期望的行为。文件是否被加载取决于require之前是否已经看到了传递给它的路径。例如,这个简单的irb会话显示您可以错误地两次要求和加载同一个文件。

$ irb
irb(main):001:0> require 'test'
=> true
irb(main):002:0> require './test'
=> true
irb(main):003:0> require './test.rb'
=> false
irb(main):004:0> require 'test'
=> false

请注意,前两行返回 true,意味着同一个文件已经被加载了两次。即使路径指向相同的位置,当使用路径时,require也不知道文件是否已经被加载过。

相反地,在这里,我们将一个目录添加到加载路径中,并要求每个 *.rb 文件的基本名称。

dir = "/path/to/directory"
$LOAD_PATH.unshift(dir)
Dir[File.join(dir, "*.rb")].each {|file| require File.basename(file) }

如果您不关心文件是否被要求多次,或者您的意图只是加载文件的内容,也许应该使用 load 而不是 require。在这种情况下使用 load,因为它更好地表达了您想要实现的目标。例如:

Dir["/path/to/directory/*.rb"].each {|file| load file }

1
这似乎是最佳解决方案,可以要求所有文件,同时避免任何意外的重复文件要求- 应标记为已接受的答案... - Mayinx
我感觉自2009年以来有些变化。现在,require需要加上./,而require_relative意识到它们都是指向同一文件的路径。 - Nakilon

17
Dir[File.join(__dir__, "/app/**/*.rb")].each do |file|
  require file
end

这将在您的本地机器和远程 (例如 Heroku) 上递归工作,不使用相对路径。


17

不同于其他答案中连接路径的方法,我使用了File.expand_path函数:

Dir[File.expand_path('importers/*.rb', File.dirname(__FILE__))].each do |file|
  require file
end

更新:

不必使用File.dirname,您可以尝试以下方法:

Dir[File.expand_path('../importers/*.rb', __FILE__)].each do |file|
  require file
end

.. 会剥离文件名中的 __FILE__


经过尝试所有其他方法后,File.expand_path似乎是最新最适合的选择。+1 - mswieboda
我绝对更喜欢这个答案而不是被接受的那个。如果你在Rails中,各种Rails.root.join的答案也适用。 - nzifnab

8
在Rails中,您可以这样做:
Dir[Rails.root.join('lib', 'ext', '*.rb')].each { |file| require file }

更新:根据@Jiggneshh Gohel的建议,已经更正并删除了斜杠。

由于 Rails.root 是一个 Pathname 实例,所以你可以在任何 Ruby 环境中执行此操作,而不仅仅是在 Rails 中(注意 Rails.root.join('lib/ext/*.rb') 读起来更加清晰)。 - DMKE
感谢您的推荐,我已经编辑并包含了您的评论。 - Dan Kohn
在Rails.root下使用斜杠(/)表示子目录,例如Rails.root.join('/lib')并不能生成正确的路径。我发现这个可以正常工作:Dir[Rails.root.join('lib', 'ext', '*.rb')].each { |file| require file } - Jignesh Gohel
@Jiggneshh Gohel,我按照你的建议删除了斜杠,谢谢。 - Dan Kohn

3

虽然我有点晚了,但我很喜欢这个一行代码的解决方案,让rails包含在app/workers/concerns中的所有内容:

Dir[ Rails.root.join *%w(app workers concerns *) ].each{ |f| require f }


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