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

____________________________________________________________________
http://www.freemail.gr - äùñåÜí õðçñåóßá çëåêôñïíéêïý ôá÷õäñïìåßïõ.
http://www.freemail.gr - free email service for the Greek-speaking.

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 Thu, 2 Feb 2006, Damphyr wrote:

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

--
happiness is not something ready-made. it comes from your own actions.
- h.h. the 14th dali lama

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 Fugal

···

On 2/1/06, Damphyr <damphyr@freemail.gr> wrote:

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

Damphyr wrote:

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

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

Damphyr wrote:

···

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

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"]

···

On 2/1/06, Damphyr <damphyr@freemail.gr> wrote:

What are my alternatives?

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

···

ara.t.howard@noaa.gov wrote:

On Thu, 2 Feb 2006, Damphyr wrote:

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

    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:

--
http://www.braveworld.net/riva

____________________________________________________________________
http://www.freemail.gr - äùñåÜí õðçñåóßá çëåêôñïíéêïý ôá÷õäñïìåßïõ.
http://www.freemail.gr - free email service for the Greek-speaking.

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

Payton Swick wrote:

···

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

Damphyr wrote:

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

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

···

On Thu, 2 Feb 2006, Damphyr wrote:
     ---
     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

--
happiness is not something ready-made. it comes from your own actions.
- h.h. the 14th dali lama

Damphyr wrote:

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

    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:

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,

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

···

ara.t.howard@noaa.gov wrote:

On Thu, 2 Feb 2006, Damphyr wrote: