Добавление метода в класс в Ruby - PullRequest
0 голосов
/ 09 января 2012

Я пытаюсь добавить метод в Observable, чтобы для включающего его класса он мог вызвать метод observe_attribute :attribute, который сгенерировал бы метод attribute= с логикой, чтобы проверить, изменилось ли значение :

module Observable
  def observe_attribute(attribute)
    raise NameError, "#{self.class} does not contain #{attribute}" unless instance_variables.include? "@#{attribute}"
    eval %" def #{attribute}=(new_value) 
              unless @#{attribute} == new_value
                changed
                notify_observers
                puts 'ok'
              end
            end
         ";
  end
end

Однако для следующего класса последний вызов observe_attribute :att приводит к NoMethodError, а прокомментированный - нет:

class Test 
  include Observable

  def initialize
    @att = 3
    #observe_attribute :att
  end

  observe_attribute :att
end

Что мне нужно сделать, чтобы последний звонок на observe_attribute работал правильно?

1 Ответ

1 голос
/ 09 января 2012

Когда вы включаете модуль, методы, содержащиеся в нем, загружаются на уровень «экземпляра» методов.

Если вы хотите включить методы из модуля в область действия класса, вы должны расширить модуль.

Или, если вам нужно и то и другое, но вы хотите упаковать его как единое «включение», вы можете расширить метод «включенного» модуля, который вы расширяете, и немного поработать с ним, расширив и включив 2 модули оттуда.

module Observable

  def self.included(other)
    other.send(:include, InstanceMethods)
    other.send(:extend, ClassMethods)
  end

  module InstanceMethods
    def some_helper
      puts "called some_helper"
    end
    def other_helper
      puts "called other_helper"
    end
  end

  module ClassMethods
    def observe_attribute(name)
      # do something useful, maybe in your case define the method
      define_method("#{name}=") do |val|
        puts "called #{name}= #{val}"
        # bla bla
        some_helper
        other_helper
        # end
        puts "done"
      end
    end
  end

end

class Foo
  include Observable
  observe_attribute :foo
end

Теперь вы можете звонить ...

o = Foo.new
o.foo = :bar

И тогда результат будет ...

called foo= bar
called some_helper
called other_helper
done
...