Forum: Ruby callbacks, events, notification in general

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Cfdeff3ac35010e4de8f85d954f24f4a?d=identicon&s=25 Damphyr (Guest)
on 2006-02-01 16:32
(Received via mailing list)
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
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-02-01 17:02
(Received via mailing list)
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!  ;-)

cheers.

-a
Cff9eed5d8099e4c2d34eae663aae87e?d=identicon&s=25 Jacob Fugal (Guest)
on 2006-02-01 17:27
(Received via mailing list)
On 2/1/06, Damphyr <damphyr@freemail.gr> 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 Fugal
Cfdeff3ac35010e4de8f85d954f24f4a?d=identicon&s=25 Damphyr (Guest)
on 2006-02-01 17:48
(Received via mailing list)
ara.t.howard@noaa.gov 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!  ;-)
Well, actually it would be a good start to remember the names of the
patterns :) .
*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
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-02-01 19:13
(Received via mailing list)
On Thu, 2 Feb 2006, Damphyr wrote:

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

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
5befe95e6648daec3dd5728cd36602d0?d=identicon&s=25 Robert Klemme (Guest)
on 2006-02-02 00:41
(Received via mailing list)
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/rdo...

Kind regards

    robert
5befe95e6648daec3dd5728cd36602d0?d=identicon&s=25 Robert Klemme (Guest)
on 2006-02-02 00:44
(Received via mailing list)
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!  ;-)
> 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
5224d56434a3d237408c217605df8a56?d=identicon&s=25 Payton Swick (Guest)
on 2006-02-02 01:00
(Received via mailing list)
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
5224d56434a3d237408c217605df8a56?d=identicon&s=25 Payton Swick (Guest)
on 2006-02-02 14:35
(Received via mailing list)
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
E20e89d58211a3631842daecc1245de2?d=identicon&s=25 Ilmari Heikkinen (Guest)
on 2006-02-02 15:45
(Received via mailing list)
On 2/1/06, Damphyr <damphyr@freemail.gr> 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"]
This topic is locked and can not be replied to.