Reputation: 136633
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
Reputation: 19
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
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
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
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
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
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
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
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