M
Max Muermann
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.senddefine_method, name, &block)
end
def StateObserver.store_method(target, new_name, old_name)
target.sendalias_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
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.senddefine_method, name, &block)
end
def StateObserver.store_method(target, new_name, old_name)
target.sendalias_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