tags:

views:

1104

answers:

5

The problem is very simple. An object needs to notify some events that might be of interest to observers.

When I sat to validate a design that I cooked up now in Ruby just to validate it.. I find myself stumped as to how to implement the object events. In .Net this would be a one-liner.. .Net also does handler method signature verification,etc. e.g.

// Object with events
public delegate void HandlerSignature(int a);
public event HandlerSignature MyEvent;
public event HandlerSignature AnotherCriticalEvent;

// Client
MyObject.MyEvent += new HandlerSignature(MyHandlerMethod); // MyHandlerMethod has same signature as delegate

Is there an EventDispatcher module or something that I am missing that I can strap on to a Ruby class ? Hoping for an answer that plays along with Ruby's principle of least surprise. An event would be the name of the event plus a queue of [observer, methodName] objects that need to be invoked when the event takes place.

+1  A: 

The Observable module?

Farrel
Not quite.. This seems to be an implementation of the Observer Design Pattern. What I need is for the Boiler Object to expose multiple events like WaterExhausted, Started, etc. and clients subscribing with thier handlers (not constrained to a single 'update' method)
Gishu
+5  A: 

Firstly, in Ruby there are no method signatures. The closest would be checking the function's arity. Duck typing requires thinking differently (slightly).

The Observable module is a start, but if you have a requirement to have multiple events from a single class it might not be enough.

This is a quick sketch. It supports methods and blocks. Modify as necessary to adapt for your code, threading approach, etc. For example, you could use method_missing to have the event name in the method name rather than having it as a parameter.

class EventBase
    def initialize
        @listeners = Hash.new
    end

    def listen_event(name, *func, &p)
        if p
            (@listeners[name] ||= Array.new) << p
        else
            (@listeners[name] ||= Array.new) << func[0]
        end
    end

    def ignore_event(name, func)
        return if [email protected]_key?(name)
        @listeners[name].delete_if { |o| o == func }
    end

    def trigger_event(name, *args)
        return if [email protected]_key?(name)
        @listeners[name].each { |f| f.call(*args) }
    end
end


class MyClass < EventBase
    def raise_event1(*args)
        trigger_event(:event1, *args)
    end

    def raise_event2(*args)
        trigger_event(:event2, *args)
    end
end

class TestListener
    def initialize(source)
        source.listen_event(:event1, method(:event1_arrival))
        source.listen_event(:event2) do |*a|
            puts "event 2 arrival, args #{a}"
        end
    end

    def event1_arrival(*a)
        puts "Event 1 arrived, args #{a}"
    end
end

s = MyClass.new
l = TestListener.new(s)

s.raise_event1("here is event 1")
s.raise_event2("here is event 2")
janm
A: 

I'd echo that there isn't a language-level analog in Ruby to .NET events. The way that rails deals with it is with ActiveSupport::Callbacks (there is an example on that page).

Matt Burke
Presumably because Ruby is a language, not a framework?
Richard Turner
if I understand this..Callbacks as in Rails are more like hooks.. where you have to derive from a class to plug-in behavior at a certain point. not quite events.. which doesnt couple the 'observer' and the 'observable'
Gishu
+3  A: 

Why not write your own event class? Something like

class Event
  def initialize
    @handlers = Array.new
  end

  def fire
    @handlers.each do |v|
      v.call
    end
  end

  def << handler
    @handlers << handler
  end
end

e = Event.new

e << lambda { puts "hello" }
e << lambda { puts "test" }
e.fire

This is just a minimal sample, but can be extended in any ways. Add parameters like sender and eventArgs in .Net, or whatever you like ;-)

D. Rexin
A: 

I wrote a gem just for this because I had exactly the same issue. Try this:

gem install ruby_events

Follow the instructions as on http://github.com/nathankleyn/ruby_events, but in a nutshell:

require 'rubygems'
require 'ruby_events'

class Example
  def initialize
    events.listen(:test_event) do |event_data|
      puts 'Hai there!'
      puts event_data
    end
  end

  def call_me
    events.fire(:test_event, 'My name is Mr Test Man!')
  end
end

e = Example.new
e.call_me # Fires the event, and our handler gets called!
Nathan Kleyn