[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Observing changes in object state

Max Muermann

8/24/2006 5:46:00 AM

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

4 Answers

Joel VanderWerf

8/24/2006 7:05:00 PM

0

Max Muermann wrote:
> 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.

Here's a somewhat different approach, but it might be interesting to
compare:

require 'observable' # see http://raa.ruby-lang.org/project/o...

class Model
extend Observable
attr_accessor :id # avoid confusion with Object#id
observable :name, :id
end

# Observer
class Watcher
def initialize m
m.when_name Object do
puts "name set to #{m.name.inspect}"
end

m.when_id Object do
puts "id set to #{m.id.inspect}"
end
# note that each method is treated separately
end
end

# change some stuff
m = Model.new
Watcher.new m
m.id='000'
m.name='test'
m.id='000'
# note this does not trigger the observer since value
# doesn't change. Use the "signal" mechanism from the
# observable lib for that behavior

__END__

Output:

name set to nil
id set to nil
id set to "000"
name set to "test"

--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Max Muermann

8/24/2006 10:06:00 PM

0

>
> # Observer
> class Watcher
> def initialize m
> m.when_name Object do
> puts "name set to #{m.name.inspect}"
> end
>
> m.when_id Object do
> puts "id set to #{m.id.inspect}"
> end
> # note that each method is treated separately

Yes, this is probably the most widely useful approach. I have found
that sometimes, though, you want to be notified if *any* of the state
in an object changes. For example, checking an model object for
"dirty" and enabling a save button or similar. In those cases, I think
a generic solution that does not explicitly require defining a watcher
method on each attribute may be useful.

By the way, thanks for bringing this up - I wasn't aware of the
observable :field notation.

Cheers,
Max

Joel VanderWerf

8/24/2006 10:57:00 PM

0

Max Muermann wrote:
>>
>> # Observer
>> class Watcher
>> def initialize m
>> m.when_name Object do
>> puts "name set to #{m.name.inspect}"
>> end
>>
>> m.when_id Object do
>> puts "id set to #{m.id.inspect}"
>> end
>> # note that each method is treated separately
>
> Yes, this is probably the most widely useful approach. I have found
> that sometimes, though, you want to be notified if *any* of the state
> in an object changes. For example, checking an model object for
> "dirty" and enabling a save button or similar. In those cases, I think
> a generic solution that does not explicitly require defining a watcher
> method on each attribute may be useful.

That's a good point, though I think I would handle it with a #dirty
method that gets called from within some of the when_ clauses, since
there are often attributes whose state is not saved.

> By the way, thanks for bringing this up - I wasn't aware of the
> observable :field notation.

It's not a general ruby notation, just something defined in this one
library.

--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Max Muermann

8/25/2006 1:31:00 AM

0

>
> > By the way, thanks for bringing this up - I wasn't aware of the
> > observable :field notation.
>
> It's not a general ruby notation, just something defined in this one
> library.
>
> --
> vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
>

Am aware of that - sorry for the sloppy language.

Max