在没有“autoload”的情况下在Ruby中自动加载类

时间:2012-01-29 13:58:01

标签: ruby lazy-loading autoload

我爱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”,则不会发生这种情况。任何想法如何解决这个问题?

3 个答案:

答案 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
相关问题