Reputation: 25652
I come from a C# background, and have just started programming in Ruby. The thing is, that I need to know how I can raise events in my classes so that various observers can be triggered when things need to happen.
The problem is the books I have on Ruby don't even mention events, let alone provide examples. Is anyone able to help me?
Upvotes: 17
Views: 17325
Reputation: 13242
Extremely simple Ruby listener. This is not exactly a replacement for .NET events, but this one is an extremely simple example of a very simple listener.
module Listenable
def listeners() @listeners ||= [] end
def add_listener(listener)
listeners << listener
end
def remove_listener(listener)
listeners.delete listener
end
def notify_listeners(event_name, *args)
listeners.each do |listener|
if listener.respond_to? event_name
listener.__send__ event_name, *args
end
end
end
end
To use:
class CowListenable
include Listenable
def speak
notify_listeners :spoken, 'moooo!'
end
end
class CowListener
def initialize(cow_listenable)
cow_listenable.add_listener self
end
def spoken(message)
puts "The cow said '#{message}'"
end
end
cow_listenable = CowListenable.new
CowListener.new(cow_listenable)
cow_listenable.speak
Output:
The cow said 'moooo!'
Upvotes: 6
Reputation: 12916
Disclosure: I am the maintainer of the event_aggregator
gem
Depending on how you want to approach the problem you could potentially use an event aggregator. This way you can publish messages of a certain type and then have your objects listen to the types you want them to receive. This can in certain cases be better than normal events because you get a very loose coupling between your objects. The event producer and listener does not need to share a reference to the other.
There is a gem that helps you with this called event_aggregator
. With it you can do the following:
#!/usr/bin/ruby
require "rubygems"
require "event_aggregator"
class Foo
include EventAggregator::Listener
def initialize()
message_type_register( "MessageType1", lambda{|data| puts data } )
message_type_register( "MessageType2", method(:handle_message) )
end
def handle_message(data)
puts data
end
def foo_unregister(*args)
message_type_unregister(*args)
end
end
class Bar
def cause_event
EventAggregator::Message.new("MessageType1", ["Some Stuff",2,3]).publish
end
def cause_another_event
EventAggregator::Message.new("MessageType2", ["Some More Stuff",2,3]).publish
end
end
f = Foo.new
b = Bar.new
b.cause_event
b.cause_another_event
# => Some Stuff
2
3
# => Some More Stuff
2
3
Be aware that it is async by default, so if you execute just this script the script might exit before the events are passed. To disable async behaviour use:
EventAggregator::Message.new("MessageType1", ["Some Stuff",2,3], false).publish
#The third parameter indicates async
Hopefully this can be helpful in your case
Upvotes: 2
Reputation: 1417
The question has already been answered, but there's an observer built right into the standard library if you want to give that a look. I've used it in the past for a small game project, and it works very well.
Upvotes: 24
Reputation: 5353
I'm not sure of exactly what you mean but you could probably use exceptions in your classes and raise them on certain "events". If you need event for GUI development then most GUI frameworks define their own event handling style.
Hope this somewhat answers you're question.
Upvotes: -1
Reputation: 2054
I tried writing a GUI library in Ruby with a little C and primarily Ruby. It ended up being so slow I gave up and never released it. But I wrote an event system for it that I tried to make easier than C#'s. I rewrote it a couple times to make it easier to use. I hope it is somewhat helpful.
class EventHandlerArray < Array
def add_handler(code=nil, &block)
if(code)
push(code)
else
push(block)
end
end
def add
raise "error"
end
def remove_handler(code)
delete(code)
end
def fire(e)
reverse_each { |handler| handler.call(e) }
end
end
# with this, you can do:
# event.add_handler
# event.remove_handler
# event.fire (usually never used)
# fire_event
# when_event
# You just need to call the events method and call super to initialize the events:
# class MyControl
# events :mouse_down, :mouse_up,
# :mouse_enter, :mouse_leave
# def initialize
# super
# end
# def when_mouse_up(e)
# # do something
# end
# end
# control = MyControl.new
# control.mouse_down.add_handler {
# puts "Mouse down"
# }
# As you can see, you can redefine when_event in a class to handle the event.
# The handlers are called first, and then the when_event method if a handler didn't
# set e.handled to true. If you need when_event to be called before the handlers,
# override fire_event and call when_event before event.fire. This is what painting
# does, for handlers should paint after the control.
# class SubControl < MyControl
# def when_mouse_down(e)
# super
# # do something
# end
# end
def events(*symbols)
# NOTE: Module#method_added
# create a module and 'include' it
modName = name+"Events"
initStr = Array.new
readerStr = Array.new
methodsStr = Array.new
symbols.each { |sym|
name = sym.to_s
initStr << %Q{
@#{name} = EventHandlerArray.new
}
readerStr << ":#{name}"
methodsStr << %Q{
def fire_#{name}(e)
@#{name}.fire(e)
when_#{name}(e) if(!e.handled?)
end
def when_#{name}(e)
end
}
}
eval %Q{
module #{modName}
def initialize(*args)
begin
super(*args)
rescue NoMethodError; end
#{initStr.join}
end
#{"attr_reader "+readerStr.join(', ')}
#{methodsStr.join}
end
include #{modName}
}
end
class Event
attr_writer :handled
def initialize(sender)
@sender = @sender
@handled = false
end
def handled?; @handled; end
end
Upvotes: 5