문제

I am trying to add a method to Observable, so that for a class that includes it, it can call the method observe_attribute :attribute that would generate a method attribute= with the logic to check to see if the value has changed:

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

However, for the following class, the last call to observe_attribute :att results in a NoMethodError, while the commented one does not:

class Test 
  include Observable

  def initialize
    @att = 3
    #observe_attribute :att
  end

  observe_attribute :att
end

What would I need to do in order for the last call to observe_attribute to work correctly?

도움이 되었습니까?

해결책

When you include a module, the methods contained in it are loaded into the "instance" level of methods.

If you want to include methods from a module into the Class scope, you have to extend the module.

Or if you need both, but you want to package it as a single "include", you can extend the "included" method of the module you're extending and do a bit of magic with it by extending and including 2 modules from there.

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

Now you can call ...

o = Foo.new
o.foo = :bar

And then the result would be ...

called foo= bar
called some_helper
called other_helper
done
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top