Callbacks, events, notification in general


#1

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


#2

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


#3

On 2/1/06, Damphyr removed_email_address@domain.invalid 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.


#4

removed_email_address@domain.invalid 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


#5

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


#6

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

#7

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

#8

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


#9

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


#10

On 2/1/06, Damphyr removed_email_address@domain.invalid 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”]