Gishu
Gishu

Reputation: 136633

What is the equivalent of .NET events in Ruby?

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.

Upvotes: 11

Views: 3509

Answers (9)

I have created a gem doing exactly what you want and surprisingly called event_dispatcher as you mentioned. I hope it gonna help someone : event_dispatcher

Upvotes: 0

Kamil Szot
Kamil Szot

Reputation: 17817

I'm a noob but Ruby seems really powerful. You can implement C# style events yourself like this:

module Observable
    class Event 
        def initialize
            @to_call = []
        end
        def fire(*arguments)
            @to_call.each { |proc| proc.call(*arguments) }
        end
        def call(proc)
            @to_call << proc
        end
        def dont_call(proc)
            @to_call.delete proc
        end
    end
    def self.append_features(cls)
        def cls.event(sym)
            define_method(sym.to_s) do
                variable_name = "@#{sym}"
                if not instance_variable_defined? variable_name then
                    instance_variable_set variable_name, Event.new
                end
                instance_variable_get variable_name
            end
        end
    end
end

# Example 

class Actor 
    include Observable
    event :whenActed
    def act
        whenActed.fire("Johnny") # fire event whenActed with parameter Johnny
    end
end

actor = Actor.new

def apploud(whom)
    print "Bravo #{whom}!\n"
end

applouder = method(:apploud)

actor.whenActed.call applouder

actor.act

Upvotes: 0

acw
acw

Reputation: 1093

Take a look into the various ruby state machine libraries. They intend to solve a large problem than just events, but may provide you with a solution.

I've used the state_machine gem with success, which does include events.

Upvotes: 1

will
will

Reputation: 5061

A quick note on this. I suggest you look at EventMachine

It is a different look a the same idea. It implements an event driven paradigm so you are one-level above the equivalent for .Net Events and consider the EventMachine module as the CLR event handler.

Also taking a step back, Ruby follows a Smalltalk processing model where any call to a method is a message (as is an Event) sent to the object (see the Send() method). EventMachine gives you is a single-threaded slice on the events. You can use something like Rack to handle threads or workers.

Upvotes: 0

Nathan Kleyn
Nathan Kleyn

Reputation: 5143

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!

Upvotes: 0

D. Rexin
D. Rexin

Reputation: 61

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 ;-)

Upvotes: 6

Matt Burke
Matt Burke

Reputation: 3326

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

Upvotes: 2

janm
janm

Reputation: 18339

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

Upvotes: 8

Farrel
Farrel

Reputation: 2381

The Observable module?

Upvotes: 2

Related Questions