Callbacks, events, notification in general

OK, bare with me, this is brainstorming of a sorts:
I want to have some kind of event notification from my classes.
Typically what I would do would be to pass a logger object and let the
class log by itself, but I’m not really satisfied with this solution in
most cases.

What I would prefer was define a callback method and give this to the
object. Then I could do whatever I want with it and not only use logs.
Mostly I want to have progress reports.

something like

class A
def set_callback notify
@notify=notify
end
def something
puts “something”
send(@notify) if @notify
end
end

def coocoo
puts “hey”
end
a=A.new
a.something
a.set_callback(:coocoo)
a.something

Now this does exactly what I want and can be wrapped up in a module to
be included for general use.
Question: Is there a “better” solution? What are my alternatives?
Cheers,
V.-


http://www.braveworld.net/riva

On Thu, 2 Feb 2006, Damphyr wrote:

something like

Question: Is there a “better” solution? What are my alternatives?
Cheers,
V.-

 harp:~ > cat a.rb
 require "observer"

 class A
   include Observable
   def something
     changed and notify_observers 42
   end
 end

 class B
   def update arg
     p arg
   end
 end

 a = A::new
 b = B::new

 a.add_observer b
 a.something


 harp:~ > ruby a.rb
 42

always a good start to read about the stdlibs! :wink:

cheers.

-a

On 2/1/06, Damphyr [email protected] wrote:

def coocoo
puts “hey”
end
a=A.new
a.something
a.set_callback(:coocoo)
a.something

I second Ara’s mention of the Observable module. That said, I’d also
simplify this particular example using a block:

class A
def set_callback( &blk )
@notify = blk
end
def something
puts “something”
@notify.call if @notify
end
end

a = A.new
a.something
a.set_callback { puts “hey” }
a.something

My general rule of thumb is that if something needs to be done, I
register a proc, but if someone needs to know, I register an
observer (using Observable).

Jacob F.

[email protected] wrote:

Mostly I want to have progress reports.
end
Now this does exactly what I want and can be wrapped up in a module to
def something
a = A::new
b = B::new

a.add_observer b
a.something


harp:~ > ruby a.rb
42

always a good start to read about the stdlibs! :wink:
Well, actually it would be a good start to remember the names of the
patterns :slight_smile: .
Then I can remember that there is an implementation in the standard
library. I’ve been staring at the screen for too long I guess.
There is a slight difference though: In Observer the observer is an
object that needs to specify the update method.
I was aiming to provide the notifier with a method, any method, to call.
Mine is a one-to-one callback between instances.
With the observer pattern you get one-to-many and the observer can
observe any observable object.
More generic and at the end more practical because I will end up naming
everything the same every time I use it.
Just need to be careful to add one/delete one instead of reassigning the
callback in order to replace an observer.
Cheers,
V.-

http://www.braveworld.net/riva

On Thu, 2 Feb 2006, Damphyr wrote:

Well, actually it would be a good start to remember the names of the
patterns :slight_smile: .

heh. i’m guilty often on that count too - the design patterns books is
pretty
dry reading to be sure.

Then I can remember that there is an implementation in the standard
library. I’ve been staring at the screen for too long I guess. There is a
slight difference though: In Observer the observer is an object that needs
to specify the update method. I was aiming to provide the notifier with a
method, any method, to call. Mine is a one-to-one callback between
instances. With the observer pattern you get one-to-many and the observer
can observe any observable object. More generic and at the end more
practical because I will end up naming everything the same every time I use
it. Just need to be careful to add one/delete one instead of reassigning
the callback in order to replace an observer.

you can leverage the exiting code:

 harp:~ > cat a.rb
 require "yaml"
 require "observer"

 module SoloObservable
   include Observable
   module Updateable
     attr_accessor "update_method"
     attr_accessor "update_context"
     def update *a, &b
       if Proc === update_method
         begin
           self.update_context = [a, b]
           instance_eval &update_method
         ensure
           self.update_context = nil
         end
       else
         send update_method, *a, &b
       end
     end
   end
   def add_observer observer, cb = nil, &cbb
     observer.extend Updateable
     observer.update_method = cb || cbb
     delete_observers
     super observer
   end
   def notify_observer *a, &b
     changed and notify_observers *a, &b
   end
 end

 class A
   include SoloObservable
   def method_that_notifies() notify_observer 42 end
 end

 class B
   def callback(arg) y "arg" => arg end
 end

 class C
   attr "answer"
   def initialize() @answer = "forty-two"  end
   def klass() self.class.name end
 end


 a, b, c = [A,B,C].map{|k| k::new}

 a.add_observer b, "callback"
 a.method_that_notifies

 puts

 a.add_observer(c){ y "update_context" => update_context.inspect, 

“klass” => klass, “answer” => answer }
a.method_that_notifies

 harp:~ > ruby a.rb
 ---
 arg: 42

 ---
 klass: C
 answer: forty-two
 update_context: "[[42], nil]"

this allows arbitrary method/block callbacks and confines to a single
observer.
blocks are evaluated in instance scope and have access to notification
params
via update_context.

hth.

-a

Damphyr wrote:

the object. Then I could do whatever I want with it and not only
send(@notify) if @notify

  include Observable

always a good start to read about the stdlibs! :wink:
More generic and at the end more practical because I will end up
naming everything the same every time I use it.
Just need to be careful to add one/delete one instead of reassigning
the callback in order to replace an observer.
Cheers,

If you want to use another method, you can use a lambda as adapter:

def observe(observable)
m = lambda {|*a| my_other_method(*a)}
class <<m
alias update call
end
observable.add_observer m
end

Kind regards

robert

Damphyr wrote:

something like

Question: Is there a “better” solution? What are my alternatives?
Cheers,
V.-

I find usage of a symbol quite clumsy. One of the drawbacks is that it
cannot address a single instance’s method easily. I’d use at least
blocks
for this. But why not simply use Observer? It’s the exact thing built
for this situation - and it’s part of the std distribution.

http://www.ruby-doc.org/stdlib/libdoc/observer/rdoc/index.html

Kind regards

robert

Not sure if it’s any help, but here’s something I’ve been using in my
code which is sort of the reverse of Observable.

This is actually just a snippet of the whole thing without comments or
some fancy features I added. I may put it up as a gem if there’s
interest.

module HandlesEvents
attr_reader :handlers

def on_event(*triggers, &handler)
@handlers ||= Hash.new
triggers.each { |trigger| @handlers[trigger] = handler }
end

def handles?(trigger)
@handlers ||= Hash.new
return true unless @handlers[trigger].nil?
false
end

def handle(trigger)
@handlers ||= Hash.new
@handlers[trigger].call(trigger) unless @handlers[trigger].nil?
end
end

-Payton

Usage, by the way:

class A
include HandlesEvents
end

a = A.new
a.on_event(:hello, :hi) { puts “Hello world!” }
a.handle(:hello) # => prints “Hello world!”

-Payton

On 2/1/06, Damphyr [email protected] wrote:

What are my alternatives?

Here’s a thought:

class Wrapper
instance_methods.grep(/^[^_]{2,2}/).each{|im|
undef_method im
}
attr_accessor :handlers

def initialize(obj)
@handlers ||= Hash.new{|h,k| h[k] = []}
@obj = obj
end

def method_missing(mname, args, &block)
if mname.to_s[0,3] == “on_”
@handlers[mname.to_s[3…-1]] << [block, args]
else
@handlers[mname.to_s].each{|handler, hargs|
handler.call(
(hargs+[mname]+args))
}
@obj.send(mname, *args, &block)
end
end

end

w = Wrapper.new(“foo”)
#=> “foo”

w.on_reverse{ puts “reversed!” }
w.on_split{|m,splitter| puts “someone’s splitting with
#{splitter.inspect}” }

w.reverse
#reversed!
#=> “oof”

w.split(//)
#someone’s splitting with //
#=> [“f”, “o”, “o”]