From: Max Muermann Date: 2006-08-24T14:45:32+09:00 Subject: Observing changes in object state Hi all, I was playing around with the observer library and thought I'd reimplement somthing I had done in Java a while back. It uses the observer pattern but automatically adds interceptors to every attribute setter method - once on the first include and then dynamically as more setter methods are added. If anybody has some comments or hints on how to implement this in a better way, I'd be delighted to hear them. You can do this: require 'state_observer' # Model to be watched for attribute changes class Model attr_accessor :name include StateObserver def id return @id end def id= nid @id = nid end end # Observer class Watcher def update( name, value ) p "#{name} set to #{value}" end end # change some stuff m = Model.new m.add_observer Watcher.new m.id='000' m.name='test' m.id='000' # add an attribute class Model attr_accessor :new_attribute end # change the atttribute m.new_attribute="new" Output: "id set to 000" "name set to test" "new_attribute set to new" StateObserver is implemented thus: require 'observer' module StateObserver include Observable # hacky bits - see RDoc for define_method for explanation def StateObserver.create_method(target, name, &block) target.send(:define_method, name, &block) end def StateObserver.store_method(target, new_name, old_name) target.send(:alias_method, new_name, old_name) end # end hacky bits def StateObserver.method_interceptor_block lambda do |target, method_name| # intercept new methods ending with '=' if method_name.to_s =~ /[a-zA-Z0-9_]=$/ # alias this method to allow method redefinition return if @skip # prevent hooking a setter twice, in case it is redefined in a subclass or similar return if respond_to? "__#{method_name}" @skip = true # alias the method store_method( target, "__#{method_name}", method_name) create_method(target, method_name) do |arg| # save current value attr_name = method_name.to_s.chop old = send(attr_name) # call original method to set new value self.send("__#{method_name}", arg) # set observer changed flag if value is different changed if arg != old # call the observer hook notify_observers( attr_name, arg ) end @skip = nil end end end def StateObserver.included( othermod ) # intercept existing setter methods othermod.public_instance_methods.each do |method_name| StateObserver.method_interceptor_block.call othermod, method_name end # intercept setters defined in the future create_method(othermod.class, :method_added) do |method_name| StateObserver.method_interceptor_block.call othermod, method_name end end end Cheers, Max