Brian Palmer wrote:
> I know you can emulate sending different messages using the 'observer' 
> standard library, but I've never felt that approach was natural, and I 
> haven't found a general-purpose Observer Pattern library that fits me, 
> so I've been working on my own approach (borrowing heavily from C#).  
> I'm still pretty ruby-nuby, though, so I'm looking for 
> suggestions/improvements.  Anyone have any thoughts?

You might find http://redshift.sourceforge.net/observable interesting. I 
should have called it "observable attribute", because observations occur 
in the context of an attr of an object rather than the whole object. 
Assignments to attrs trigger code in observers, if the assigned value 
matches a pattern (and is not == the old value).

It was specifically designed for GUI code (Fox in particular). For 
instance, a dialog with some controls gets wired up to another window 
with display objects so that the controls stay in sync with the objects.

One limitation, in comaprison with your idea: you can't add unlimited 
observer code--you can only add one observer per observing object, per 
pattern. (Each observable attr has a hash that maps [observer, pattern] 
=> proc.) I tend to think this is sensible, but YMMV.

Here's how your example would look with observable attrs:

require 'observable'

class TextBox
   extend Observable

   observable :text

   def initialize txt = ""
     self.text = txt
   end

   def key=(k)
     # Assume keypress is an instantaneous event, so don't assign. We
     # can still observe the event.
   end

   observable :key
   # Do this after defining #key= so that the no-op behavior above
   # is retained. The #key= method defined by observable will first
   # call the pre-existing #key= method (no-op, in this case), then
   # notify observers.
end

def my_key_press(k)
   puts "KEY: #{k}"
end

box = TextBox::new
box.when_text // do puts "Text changed!" end
box.when_key // do |k| my_key_press(k) end
   # The pattern matching uses #===, so patterns can be regexes,
   # classes, etc.

puts "---"

box.text = "New text!"

puts "---"

box.key = 'j'
box.key = 'j'
p box.key

puts "---"

box.cancel_when_key //, self # self is the observer
box.key = 'k' # no effect

puts "---"

box.when_text /42/ do puts "The answer!" end
box.text = "42"

__END__

Output:

Text changed!
---
Text changed!
---
KEY: j
KEY: j
nil
---
---
Text changed!
The answer!