KVO in Ruby

Discussion in 'Ruby' started by Joshua Ballanco, Jun 7, 2007.

  1. Hey all!

    I'm still relatively new to Ruby, so forgive me if this seems like a
    naive (or even stupid) question, but...

    Is there a good method for implementing Key-Value Observing in Ruby?

    Basically, what I'd like to be able to do is this:

    class Actor
    attr_accessor :var_to_change

    def change_var
    @var_to_change = 1
    100.times {@var_to_change += 1}
    end
    end

    class Observer
    def initialize(act)
    @actor = act
    act.add_observer(self, var_to_change)
    end

    def observeValue(var, sender)
    if sender == @actor
    puts "The value is now #{var}"
    end
    end
    end

    act = Actor.new
    obs = Observer.new(act)
    thr = Thread.new {act.change_var}
    thr.join

    The resulting output would be:
    The value is now 1
    The value is now 2
    ...and so on.

    I realize that there are all sorts of ways that similar functionality
    could be achieved, but something about KVO is really attractive
    (especially since Ruby is already really good at KVC). It seems to me
    that this would be a fairly easy to implement as a module, but I don't
    want to have to do the work if someone else already has. Does anyone
    know how RubyCocoa handles KVO? or if it does?

    Thanks for any help/comments/scornful rebukes. ;-)

    -Josh

    --
    Posted via http://www.ruby-forum.com/.
    Joshua Ballanco, Jun 7, 2007
    #1
    1. Advertising

  2. Joshua Ballanco

    Zachary Holt Guest

    Zachary Holt, Jun 7, 2007
    #2
    1. Advertising

  3. 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.

    --
    Posted via http://www.ruby-forum.com/.
    Joshua Ballanco, Jun 7, 2007
    #3
  4. 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
    Joel VanderWerf, Jun 7, 2007
    #4
  5. Joshua Ballanco

    Zachary Holt Guest

    On Jun 7, 2007, at 7:53 AM, 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?

    I suppose you wouldn't have to modify the String class, if you just
    want to observe one string.

    require 'observer'

    s = "Something fishy"
    class << s
    include( Observable )
    #etc..
    end

    > 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.

    Could be. It's been a while since I played with NSAnything.


    > 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.

    You could do something like this. I'm not sure whether masking the
    class like this is kosher, however. It would take some tinkering to
    make it work with more complex classes, but you get the idea.

    require 'observer'

    class Observee
    include Observable
    def initialize( thing )
    @thing = thing
    end
    def method_missing( meth, *args )
    changed true
    notify_observers( meth, @thing.dup, @thing.send( meth, *args ) )
    end
    def class ; @thing.class ; end
    end

    class Observer
    def update( meth, ol, ne )
    puts "#{meth} #{
    ol == ne ? 'did not change' : 'changed'
    } the #{ol.class.name}"
    end
    end

    s = Observee.new( "Something fishy" )
    puts s.class.name
    so = Observer.new
    s.add_observer( so )
    s.gsub!( /([aeiouy])/, 'Y' )
    s << ' Mmm. Tasty'
    s.gsub!( /([aeiouy])/, '' )

    h = Observee.new( :a => 'b' )
    puts h.class.name
    ho = Observer.new
    h.add_observer( ho )
    h[:c] = 'd'
    h.delete( :a )
    Zachary Holt, Jun 7, 2007
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    0
    Views:
    204
  2. anne001
    Replies:
    1
    Views:
    402
  3. Phrogz
    Replies:
    4
    Views:
    220
    Austin Ziegler
    Sep 6, 2006
  4. roschler
    Replies:
    0
    Views:
    174
    roschler
    Oct 16, 2006
  5. Andras Zalavari

    MacRuby KVO array ERROR?

    Andras Zalavari, Nov 14, 2010, in forum: Ruby
    Replies:
    1
    Views:
    123
    Ryan Davis
    Nov 14, 2010
Loading...

Share This Page