Почему включение этого модуля не отменяет динамически генерируемый метод? - PullRequest
6 голосов
/ 20 января 2011

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

В приведенном ниже примере ассоциация Ripple добавляет метод rows= в таблицу. Я хочу вызвать этот метод, но потом сделаю еще кое-что.

Я создал модуль для переопределения метода, полагая, что row= модуля сможет вызвать super для использования существующего метода.

class Table

  # Ripple association - creates rows= method
  many :rows, :class_name => Table::Row

  # Hacky first attempt to use the dynamically-created
  # method and also do additional stuff - I would actually
  # move this code elsewhere if it worked
  module RowNormalizer
    def rows=(*args)
      rows = super
      rows.map!(&:normalize_prior_year)
    end
  end
  include RowNormalizer

end

Тем не менее, мой новый rows= никогда не вызывается, о чем свидетельствует тот факт, что если я вызову исключение внутри него, ничего не произойдет.

Я знаю, что модуль включается, потому что, если я вставлю это в него, моё исключение поднимется.

      included do
        raise 'I got included, woo!'
      end

Кроме того, если вместо rows= модуль определяет somethingelse=, этот метод может быть вызван.

Почему мой метод модуля не перекрывает динамически генерируемый?

Ответы [ 3 ]

11 голосов
/ 20 января 2011

Давайте проведем эксперимент:

class A; def x; 'hi' end end
module B; def x; super + ' john' end end
A.class_eval { include B }

A.new.x
=> "hi" # oops

Почему это? Ответ прост:

A.ancestors
=> [A, B, Object, Kernel, BasicObject]

B предшествует A в цепочке предков (вы можете думать об этом как B, являющийся внутри A). Поэтому A.x всегда имеет приоритет над B.x.

Однако это можно обойти:

class A
  def x
    'hi'
  end
end

module B
  # Define a method with a different name
  def x_after
    x_before + ' john'
  end

  # And set up aliases on the inclusion :)
  # We can use `alias new_name old_name`
  def self.included(klass)
    klass.class_eval {
      alias :x_before :x 
      alias :x :x_after
    }
  end
end

A.class_eval { include B }

A.new.x #=> "hi john"

С ActiveSupport (и, следовательно, Rails) этот шаблон реализован как alias_method_chain(target, feature) http://apidock.com/rails/Module/alias_method_chain:

module B
  def self.included(base)
    base.alias_method_chain :x, :feature
  end

  def x_with_feature
    x_without_feature + " John"
  end
end

Обновление Ruby 2 поставляется с Module # prepend , который переопределяет методы A, что делает этот хак alias ненужным для большинства случаев использования.

2 голосов
/ 20 января 2011

Почему мой метод модуля не перекрывает динамически генерируемый?

Потому что наследование не так. Методы, определенные в классе, переопределяют методы, унаследованные от других классов / модулей, а не наоборот.

В Ruby 2.0 есть Module#prepend, который работает так же, как Module#include, за исключением того, что он вставляет модуль как подкласс вместо суперкласса в цепочке наследования.

0 голосов
/ 20 января 2011

Если вы extend экземпляр класса, вы сможете это сделать.

class A
  def initialize
    extend(B)
  end
  def hi
    'hi'
  end
end
module B
  def hi
    super[0,1] + 'ello'
  end
end

obj = A.new
obj.hi #=> 'hello'
...