Рубин: класс C включает в себя модуль M; включение модуля N в M не влияет на C. Что дает? - PullRequest
8 голосов
/ 31 октября 2009

Более подробно, у меня есть модуль 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

Ответы [ 3 ]

3 голосов
/ 01 ноября 2009

В вашу проблему приходят два решения. Ни один из них не очень красив:

a) Пройдите все классы, которые включают Enumerable, и включите в них также Narf. Примерно так:

ObjectSpace.each(Module) do |m|
  m.send(:include, Narf) if m < Enumerable
end

Хотя это довольно хакерски.

b) Добавить функциональность в Enumerable напрямую вместо его собственного модуля. Это на самом деле может быть хорошо, и это будет работать. Такой подход я бы рекомендовал, хотя он и не идеален.

3 голосов
/ 01 ноября 2009
Массив класса

уже смешан с модулем Enumerable, который еще не включает ваш модуль Narf. По этой причине он выдает (в основном свои методы) n ошибку.

, если вы снова включите Enumerable в Array, т. Е.

class Array
  include Enumerable
end

Миксин делает ссылку из класса на включенный модуль, который в этом конкретном объектном пространстве имеет все методы, которые должны быть включены. Если вы измените какой-либо из существующих методов модуля, все классы, включающие этот модуль, будут отражать изменения.

Но если вы добавляете новые модули в уже существующий модуль, вам необходимо повторно включить модуль, чтобы справка могла быть обновлена.

2 голосов
/ 01 ноября 2009

При написании моего вопроса, я неизбежно сталкивался с ответом. Вот что я придумал. Дайте мне знать, если я пропустил очевидное, гораздо более простое решение.

Проблема, похоже, заключается в том, что включение модуля выравнивает предков включенного модуля и включает , что . Таким образом, поиск метода не является полностью динамическим, цепочка предков включенных модулей никогда не проверяется.

На практике 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 для дополнительного удовольствия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...