При написании моего вопроса, я неизбежно сталкивался с ответом. Вот что я придумал. Дайте мне знать, если я пропустил очевидное, гораздо более простое решение.
Проблема, похоже, заключается в том, что включение модуля выравнивает предков включенного модуля и включает , что . Таким образом, поиск метода не является полностью динамическим, цепочка предков включенных модулей никогда не проверяется.
На практике 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!
Теперь на GitHub и Gemcutter для дополнительного удовольствия.