Joshua Ballanco wrote:
> Zachary Holt wrote:
>> On Jun 6, 2007, at 10:47 PM, Joshua Ballanco wrote:
>>
>>> Hey all!
>> Hey
>>
>>> Is there a good method for implementing Key-Value Observing in Ruby?
>> Check out the Observable module.
>>
>> http://ruby-doc.org/core/classes/Observable.html
> 
> Hmm...how did I miss that? This should do very well for the project I'm 
> currently working on.
> 
> However, looking through the Observable module, I see one major 
> downside: it's not automatic. That is, the Observable module should work 
> well enough for custom classes, but what about Built-in and Standard 
> Library classes? If, say, you wanted to be notified when the length of a 
> string was changed, you would have to extend the String class, but then 
> also implement a method to constantly observe the length...or am I 
> missing something here? The way I understand how this works in Cocoa is 
> that isa-swizzling is used to replace the class of the property being 
> observed with a thin pseudo-class that notifies any registered observers 
> when a change is made, then passes the change to the original class.
> 
> Is there a good reason not to do things this way? to do things this way? 
> The only KVO implementation that I've ever used is Cocoa's, so I'm just 
> curious about how/why it's done in Ruby. If I'm making a big fuss over 
> nothing please feel free to say so.
> 

It will be difficult to get automatic notification of a change in the 
length of a string, since there are so many ways that can happen, and it 
happens in core code. (For example, the C function rb_str_append() gets 
called from other C functions, so there is no way to hook in a pure ruby 
observer.)

However, there are some cases where a library class has an attr_writer 
(or other method) and all you want is notification of changes that go 
through that method. And, of course if you are developing the library 
yourself you can force all changes to go through a method.

In these cases, you might want to take a look at my observable library 
(really, it should be called observable-attr or something):

http://raa.ruby-lang.org/project/observable/
http://redshift.sourceforge.net/observable/

Unlike the standard Observer library, which operates at the level of 
entire objects, this library operates on individual methods. Also, 
notification is automatic in the sense that the observed object doesn't 
have to call notify_observers. Further, each observer can register more 
than one "when" clause that is called just when the new value matches 
(in the sense of #===) some pattern, class, or other matching object.

I've found this useful primarily in fxruby (the foxtails library uses it 
to make data targets more friendly and responsive).

Here's an example:

   require 'observable'

   # A prexisting class with a method that doesn't expect to be observed.
   class Base
     attr_writer :foo
   end

   class Speaker < Base
     extend Observable

     # make the inherited method :foo be observable and define an
     # additional observable attribute, :bar.
     observable :foo, :bar

     def run
       self.foo = "1"
       self.bar = [4,5,6]
       self.foo = "2"
       self.bar << 7 # Caution: no notification here!
       self.bar += [8] # notification
       self.foo = "3"
       @foo = "4" # No notification here, since writer wasn't called
     end
   end

   class Listener
     def initialize(speaker)
       speaker.when_foo /2/ do |v|
         puts "#{self} saw foo change to have a 2 in #{v.inspect}"
       end

       speaker.when_foo /\d/ do |v|
         puts "#{self} saw foo change to have a digit in #{v.inspect}"
       end

       # This would override the first when_foo clause
       #speaker.when_foo /2/ do |v|
       #  puts "#{self} saw foo change to have a 2 in #{v.inspect} 
[overridden]"
       #end

       # listen for _any_ changes (note that #=== is used to match value,
       # so Object matches everything, including the initial nil)
       speaker.when_bar Object do |v, old_v|
         puts "#{self} saw bar change from #{old_v.inspect} to #{v.inspect}"
       end
     end
   end

   sp = Speaker.new
   Listener.new(sp)
   sp.run

__END__

Output:

#<Listener:0xb7a56628> saw bar change from nil to nil
#<Listener:0xb7a56628> saw foo change to have a digit in "1"
#<Listener:0xb7a56628> saw bar change from nil to [4, 5, 6]
#<Listener:0xb7a56628> saw foo change to have a 2 in "2"
#<Listener:0xb7a56628> saw foo change to have a digit in "2"
#<Listener:0xb7a56628> saw bar change from [4, 5, 6, 7] to [4, 5, 6, 7, 8]
#<Listener:0xb7a56628> saw foo change to have a digit in "3"

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