我爱the autoload functionality of Ruby;但是,它是going away in future versions of Ruby,因为它从来都不是线程安全的。
所以现在我想假装它已经消失并且在没有它的情况下编写我的代码,自己实现延迟加载机制。我想以最简单的方式实现它(我现在不关心线程安全性)。 Ruby应该允许我们这样做。
让我们从扩充课程“const_missing
:
class Dummy
def self.const_missing(const)
puts "const_missing(#{const.inspect})"
super(const)
end
end
当我们尝试引用缺少的“Dummy”下的常量时,Ruby将调用这个特殊方法,例如,如果我们尝试引用“Dummy :: Hello”,它将使用符号{{const_missing
调用:Hello
1}}。这正是我们所需要的,所以让我们更进一步:
class Dummy
def self.const_missing(const)
if :OAuth == const
require 'dummy/oauth'
const_get(const) # warning: possible endless loop!
else
super(const)
end
end
end
现在,如果我们引用“Dummy :: OAuth”,它将需要“dummy / oauth.rb”文件,该文件应该定义“Dummy :: OAuth”常量。当我们调用const_get
时,可能会出现无限循环(因为它可以在内部调用const_missing
),但是防止这种情况超出了这个问题的范围。
最大的问题是,如果在顶级命名空间中存在名为“OAuth”的模块,整个解决方案就会崩溃。引用“Dummy :: OAuth”将跳过其{{1}并从顶层返回“OAuth”。大多数Ruby实现也会对此发出警告:
const_missing
This was reported as a problem way back in 2003但我无法找到Ruby核心团队曾经关注此事的证据。今天,大多数流行的Ruby实现都具有相同的行为。
问题是warning: toplevel constant OAuth referenced by Dummy::OAuth
被静默跳过,而支持顶级命名空间中的常量。如果使用Ruby的const_missing
功能声明“Dummy :: OAuth”,则不会发生这种情况。任何想法如何解决这个问题?
答案 0 :(得分:5)
这是在一段时间之前在Rails票证中提出的,当我调查它时,似乎没有办法绕过它。问题是Ruby会在调用const_missing
之前搜索祖先,并且因为所有类都有Object
作为祖先,所以总会找到任何顶级常量。如果您可以限制自己只使用命名空间模块,那么它将起作用,因为它们没有Object
作为祖先,例如:
>> class A; end
>> class B; end
>> B::A
(irb):3: warning: toplevel constant A referenced by B::A
>> B.ancestors
=> [B, Object, Kernel, BasicObject]
>> module C; end
>> module D; end
>> D::C
NameError: uninitialized constant D::C
>> D.ancestors
=> [D]
答案 1 :(得分:0)
如果我在const_get
内使用const_missing
,我会在ree 1.8.7(您没有提及特定版本)上遇到问题,但如果我使用::
则不会。我不喜欢使用eval
,但它确实在这里工作:
class Dummy
def self.const_missing(const)
if :OAuth == const
require 'dummy/oauth'
eval "self::#{const}"
else
super(const)
end
end
end
module Hello
end
Dummy.const_get :Hello # => ::Hello
Dummy::Hello # => Dummy::Hello
我希望Module
使用::
方法,这样您就可以self.send :"::", const
。
答案 2 :(得分:0)
延迟加载是一种非常常见的设计模式,您可以通过多种方式实现它。喜欢:
class Object
def bind(key, &block)
@hooks ||= Hash.new{|h,k|h[k]=[]}
@hooks[key.to_sym] << [self,block]
end
def trigger(key)
@hooks[key.to_sym].each { |context,block| block.call(context) }
end
end
然后你可以
bind :json do
require 'json'
end
begin
JSON.parse("[1,2]")
rescue
trigger :json
retry
end