更详细地说,我有一个模块Narf
,它为一系列类提供基本功能。具体来说,我想影响继承Enumerable
的所有类。我include Narf
中的Enumerable
。
Array
是一个默认包含Enumerable
的类。然而,它并未受到模块中Narf
的延迟包含的影响。
有趣的是,包含后定义的类从Narf
获得Enumerable
。
# This module provides essential features
module Narf
def narf?
puts "(from #{self.class}) ZORT!"
end
end
# I want all Enumerables to be able to Narf
module Enumerable
include Narf
end
# Fjord is an Enumerable defined *after* including Narf in Enumerable
class Fjord
include Enumerable
end
p Enumerable.ancestors # Notice that Narf *is* there
p Fjord.ancestors # Notice that Narf *is* here too
p Array.ancestors # But, grr, not here
# => [Enumerable, Narf]
# => [Fjord, Enumerable, Narf, Object, Kernel]
# => [Array, Enumerable, Object, Kernel]
Fjord.new.narf? # And this will print fine
Array.new.narf? # And this one will raise
# => (from Fjord) ZORT!
# => NoMethodError: undefined method `narf?' for []:Array
答案 0 :(得分:3)
class Array已经与Enumerable模块混合使用,该模块还没有包含你的Narf模块。这就是它抛出(基本上是它的方法)n错误的原因。
如果再次在数组中包含Enumerable,即
class Array
include Enumerable
end
混合使用从类到引用模块的引用,在该特定对象空间中包含所有方法。如果修改模块的任何现有方法,则包含该模块的所有类都将反映更改。
但是,如果您向现有模块添加新模块,则必须重新包含该模块,以便更新参考。
答案 1 :(得分:3)
您可以想到两个解决问题的方法。他们都不是真的很漂亮:
a)浏览包含Enumerable的所有类,并使它们也包括Narf。像这样:
ObjectSpace.each(Module) do |m|
m.send(:include, Narf) if m < Enumerable
end
虽然这很苛刻。
b)直接将功能添加到Enumerable而不是自己的模块。这实际上可能没问题,它会起作用。这是我推荐的方法,虽然它也不完美。
答案 2 :(得分:2)
在写我的问题时,我不可避免地遇到了一个答案。这就是我想出来的。如果我错过了一个明显的,更简单的解决方案,请告诉我。
问题似乎是模块包含会使所包含模块的祖先变平,并包含 。因此,方法查找不是完全动态的,从不检查包含模块的祖先链。
在实践中,Array
知道Enumerable
是一个祖先,但它并不关心Enumerable
中当前包含的内容。
好处是你可以再次include
个模块,它会重新计算模块的祖先链,并包含整个事物。因此,在定义并包含Narf
之后,您可以重新打开Array
并再次添加Enumerable
,它也将获得Narf
。
class Array
include Enumerable
end
p Array.ancestors
# => [Array, Enumerable, Narf, Object, Kernel]
现在让我们概括一下:
# Narf here again just to make this example self-contained
module Narf
def narf?
puts "(from #{self.class}) ZORT!"
end
end
# THIS IS THE IMPORTANT BIT
# Imbue provices the magic we need
class Module
def imbue m
include m
# now that self includes m, find classes that previously
# included self and include it again, so as to cause them
# to also include m
ObjectSpace.each_object(Class) do |k|
k.send :include, self if k.include? self
end
end
end
# imbue will force Narf down on every existing Enumerable
module Enumerable
imbue Narf
end
# Behold!
p Array.ancestors
Array.new.narf?
# => [Array, Enumerable, Narf, Object, Kernel]
# => (from Array) ZORT!