今天我遇到了一个奇怪的问题: 在模块上出现“遗漏方法”错误,但方法就在那里,并且需要定义模块的文件。经过一番搜索,我找到了一个循环依赖,其中2个文件需要彼此,现在我假设ruby默默地中止循环要求。
编辑开始:示例
档案'a.rb':
require './b.rb'
module A
def self.do_something
puts 'doing..'
end
end
档案'b.rb':
require './a.rb'
module B
def self.calling
::A.do_something
end
end
B.calling
执行b.rb会给出b.rb:5:in 'calling': uninitialized constant A (NameError)
。对于两个文件都必须存在这些要求,因为它们要从命令行自行运行(我省略了该代码以保持简短)。
所以B.calling必须在那里。一种可能的解决方案是将需求包装在if __FILE__ == $0
中,但这似乎不是正确的方法。
编辑结束
避免这些难以发现的错误(顺便提一下,如果要求抛出异常会不会更好?),是否有关于如何构建项目以及在哪里需要什么的指导/规则?例如,如果我有
module MainModule
module SubModule
module SubSubModule
end
end
end
我应该在哪里需要子模块?所有在主要的,或只有主要的子和子的子母?
任何帮助都会非常好。
forforfs回答和评论中讨论了为什么会发生这种情况。
到目前为止,最佳实践(如lain所指出或暗示的)似乎如下(如果我错了请纠正我):
MainModule
需要SubModule
,而Submodule
需要SubSubModule
)答案 0 :(得分:10)
在一段时间之后在Ruby邮件列表上询问这个问题后,当我以前在我的库中有一个文件只是为了要求时,我改为这两个规则:
如果某个文件需要同一个库中另一个文件的代码,我会在需要该代码的文件中使用require_relative
。
如果文件需要来自不同库的代码,我会在需要代码的文件中使用require
。
据我所知,Ruby需要按顺序要求,因此循环依赖无关紧要。
(Ruby v1.9.2)
回答有关显示循环依赖性问题的示例的评论:
实际上,示例的问题并不是需求是循环的,而是在需求完成之前调用B.calling
。如果从b.rb中删除B.calling,它可以正常工作。例如,在代码文件中没有B.calling但之后运行的irb:
$ irb
要求'/Volumes/RubyProjects/Test/stackoverflow8057625/b.rb'
=>真正
B.calling
做..
=>没有
答案 1 :(得分:6)
您希望已经知道的几件基本事情:
Ruby是解释的,而不是编译的,所以你不能执行解释器没有看到的任何代码。
require
只需将文件中的代码插入到程序的那一点,换句话说,程序顶部的require
将在{{1}之前解释在底部。
(注意:编辑为需要声明行为的帐户)
所以,如果你这样做:
require
这就是ruby解释器会看到并执行的内容:
ruby a.rb
如果您首先运行b,#load file b.rb <- from require './b.rb' in 'a.rb' file
#load file a.rb <- from require './a.rb' in 'b.rb' file
#this runs because a.rb has not yet been required
#second attempt to load b.rb but is ignored <- from require './b.rb' in 'a.rb' file
#finish loading the rest of a.rb
module A
def self.do_something
puts 'doing..'
end
end
#finish loading the rest of b.rb
module B
def self.calling
::A.do_something
end
end
B.calling
#Works because everything is defined
,解释器会看到:
ruby b.rb
希望这能解释其他人给你的好答案,如果你仔细想想,为什么很难回答你关于在哪里提出要求陈述的最后一个问题。使用Ruby,您需要文件而不是模块,因此将代码放入代码的位置取决于文件的组织方式。
如果你绝对需要能够定义模块并且方法以随机顺序执行,那么你可以实现类似的东西来收集对尚不存在的模块的调用,然后在它们出现时调用它们。
#load file a.rb <- from require './a.rb' in 'b.rb' file
#load file b.rb <- from require './b.rb' in 'a.rb' file
#this runs because b.rb has not yet been required
#second attempt to load a.rb but is ignored <- from require './a.rb' in 'b.rb' file
#finish loading the rest of b.rb
module B
def self.calling
::A.do_something
end
end
B.calling #NameError, ::A.do_something hasn't been defined yet.
请务必先定义延迟模块,然后再调用module Delay
@@q = {}
def self.call_mod(*args) #args format is method_name, mod_name, *args
mod_name = args.shift
method_name = args.shift
#remaining args are still in args
mod = Object.const_get(mod_name.to_sym)
mod.send(method_name.to_sym, *args)
end
def self.exec(mod_name, *args)
begin
args.unshift(mod_name)
self.call_mod(*args)
rescue NameError, NoMethodError
@@q[mod_name] ||= []
@@q[mod_name] << args
end
end
def self.included(mod)
#get queued methods
q_list = @@q[mod.name.to_sym]
return unless q_list
#execute delayed methods now that module exists
q_list.each do |args|
self.call_mod(*args)
end
end
end
来使用B.calling
。所以如果你在延迟模块之后有这个:
Delay.exec(:B, :calling, any_other_args)
结果:
Delay.exec(:B, :calling) #Module B is not defined
module B
def self.calling
::A.do_something
end
include Delay #must be *after* any callable method defs
end
module A
def self.do_something
puts 'doing..'
end
include Delay #must be *after* any callable method defs
end
最后一步是将代码分解为文件。一种方法可能是有三个文件
#=> doing..
只要您确保delay.rb #holds just Delay module
a.rb #holds the A module and any calls to other modules
b.rb #holds the B module and any calls to other modules
是模块文件的第一行(a.rb和b.rb)并且模块末尾包含Delay,那么事情就应该有效。
最后注意:如果您无法将定义代码与模块执行调用分离,则此实现才有意义。