Ruby: Mixin, который добавляет динамические методы экземпляров, имена которых создаются с помощью метода класса - PullRequest
6 голосов
/ 07 апреля 2011

У меня есть следующее:

module Thing
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    attr_reader :things

    def has_things(*args)
      options = args.extract_options! # Ruby on Rails: pops the last arg if it's a Hash

      # Get a list of the things (Symbols only)
      @things = args.select { |p| p.is_a?(Symbol) }

      include InstanceMethods
    end
  end

  module InstanceMethods
    self.class.things.each do |thing_name| # !!! Problem is here, explaination below
      define_method "foo_for_thing_#{thing_name}" do
        "bar for thing #{thing_name}"
      end
    end
  end
end

В другом классе, который смешивает модуль Thing:

class Group
  has_things :one, :two, :option => "something"
end

При вызове has_things внутри класса я хотел быиметь динамические методы экземпляров "foo_for_thing_one" и "foo_for_thing_two".Например:

@group = Group.new
@group.foo_for_thing_one # => "bar for thing one"
@group.foo_for_thing_two # => "bar for thing two"

Однако я получаю следующую ошибку:

`<module:InstanceMethods>': undefined method `things' for Module:Class (NoMethodError)

Я понимаю, что "self" в проблемной строке указано выше (первая строка модуля InstanceMethods)ссылается на модуль InstanceMethods.

Как я могу ссылаться на метод класса "вещи" (который возвращает [: one,: two] в этом примере), чтобы я мог выполнить цикл и создать динамические методы экземпляра для каждого?Благодарю.Или, если у вас есть другие предложения для достижения этой цели, пожалуйста, дайте мне знать.

1 Ответ

19 голосов
/ 07 апреля 2011

Быстрый ответ:

Поместите содержимое InstanceMethods в определение метода has_things и удалите модуль InstanceMethods.

Лучший ответ:

Использование анти-паттерна InstanceMethods-ClassMethods здесь особенно неоправданно, а катапультирование груза добавило вам путаницы в отношении объема и контекста. Сделайте самое простое, что может сработать. Не копируйте чужой код без критического размышления.

Единственный модуль, который вам нужен, - это ClassMethods, которому нужно дать полезное имя, и оно не должно включаться, а должно использоваться для расширения класса, которому вы хотите предоставить функциональность has_things. Вот самая простая вещь, которая могла бы работать:

module HasThings
  def has_things(*args)
    args.each do |thing|
      define_method "thing_#{thing}" do
        "this is thing #{thing}"
      end
    end
  end
end

class ThingWithThings
  extend HasThings
  has_things :foo
end

ThingWithThings.new.thing_foo # => "this is thing foo"

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

...