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?
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.
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
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!'
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
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.
Related
I have a wrapper class which redefines a method of the wrapped class. Is there any way the wrapper's state can be accessed from inside the override method?
class WidgetWrapper
attr_accessor :result_saved_by_widget
def initialize(widget)
#widget = widget
# we intercept the widget's usual "save" method so we can see
# what the widget tries to save
def #widget.save_result(result) # this override works fine ...
OUTER.result_saved_by_widget = result # .. but I need something like this inside it!
end
end
def call
widget.calculate # this will call "save_result" at some stage
end
end
# How it gets used
wrapper = Wrapper.new(Widget.new)
wrapper.call
puts wrapper.result_saved_by_widget
Based on your example, I would extend the object with a module:
module WidgetExtension
attr_accessor :results_saved_by_widget
def save_result(result)
#results_saved_by_widget = result
super
end
end
w = Widget.new
w.extend(WidgetExtension)
w.calculate
w.results_saved_by_widget #=> stored value
Solved this with a perfectly stupid hack - injecting the wrapper object beforehand, using instance_variable_set.
class WidgetWrapper
attr_accessor :result_saved_by_widget
def initialize(widget)
#widget = widget
#widget.instance_variable_set :#wrapper, self
# we intercept the widget's usual "save" method so we can see
# what the widget tries to save
def #widget.save_result(result) # this override works fine ...
#wrapper.result_saved_by_widget = result # ... and this works too :)
end
end
def call
widget.calculate # this will call "save_result" at some stage
end
end
# How it gets used
wrapper = Wrapper.new(Widget.new)
wrapper.call
puts wrapper.result_saved_by_widget
I don't quite understand your question but I think did something quite similar in the past, maybe the following lines can help you :
documents_to_wrap.each do |doc|
doc.define_singleton_method(:method){override_code}
tmp = doc.instance_variable_get(:#instance_var).
doc.instance_variable_set(:#other_instance_var, tmp.do_something)
end
Actually, it's not that hard. A couple of points:
You probably want to call the original save_result. Otherwise, it's not much of a wrapper.
You need to use closure to capture current lexical context (meaning, memorize that we're in WidgetWrapper)
class Widget
def calculate
save_result(3)
end
def save_result(arg)
puts "original save_result: #{arg}"
end
end
class WidgetWrapper
attr_accessor :result_saved_by_widget, :widget
def initialize(widget)
#widget = widget
wrapper = self # `self` can/will unpredictably change.
#widget.define_singleton_method :save_result do |result|
wrapper.result_saved_by_widget = result
super(result)
end
end
def call
widget.calculate
end
end
# How it gets used
wrapper = WidgetWrapper.new(Widget.new)
wrapper.call
puts 'intercepted value'
puts wrapper.result_saved_by_widget
# >> original save_result: 3
# >> intercepted value
# >> 3
Let say we have classes A,B,C.
A
def self.inherited(sub)
# meta programming goes here
# take class that has just inherited class A
# and for foo classes inject prepare_foo() as
# first line of method then run rest of the code
end
def prepare_foo
# => prepare_foo() needed here
# some code
end
end
B < A
def foo
# some code
end
end
C < A
def foo
# => prepare_foo() needed here
# some code
end
end
As you can see I am trying to inject foo_prepare() call to each one of foo() methods.
How can that be done?
Also I have been thinking about overriding send class in class A that way I would run foo_prepare and than just let send (super) to do rest of the method.
What do you guys think, what is the best way to approach this problem?
Here's a solution for you. Although it's based on module inclusion and not inheriting from a class, I hope you will still find it useful.
module Parent
def self.included(child)
child.class_eval do
def prepare_for_work
puts "preparing to do some work"
end
# back up method's name
alias_method :old_work, :work
# replace the old method with a new version, which has 'prepare' injected
def work
prepare_for_work
old_work
end
end
end
end
class FirstChild
def work
puts "doing some work"
end
include Parent # include in the end of class, so that work method is already defined.
end
fc = FirstChild.new
fc.work
# >> preparing to do some work
# >> doing some work
I recommend Sergio's solution (as accepted). Here is what I did which fit my needs.
class A
def send(symbol,*args)
# use array in case you want to extend method covrage
prepare_foo() if [:foo].include? symbol
__send__(symbol,*args)
end
end
or
class A
alias_method :super_send, :send
def send(symbol,*args)
prepare_foo() if [:foo].include? symbol
super_send(symbol,*args)
end
end
As of Ruby 2.0 you can use 'prepend' to simplify Sergio's solution:
module Parent
def work
puts "preparing to do some work"
super
end
end
class FirstChild
prepend Parent
def work
puts "doing some work"
end
end
fc = FirstChild.new
fc.work
This allows a module to override a class's method without the need for alias_method.
hereafter is my piece of code that I want to simplify in order to avoid passing an extra argument on each call. In fact, my usecase is that M is a user library without the definition of context argument on each method. check is a method that is not defined by the user.
# User code
module M
def do_something(context)
puts "Called from #{context}"
context.check
end
module_function :do_something
end
# Application code
class Bar
def check
puts "Checking from #{self}..."
end
end
class Foo < Bar
def do_stuff(scope, method)
scope.send method, self
end
end
# Executed by user
Foo.new.do_stuff M, :do_something
Is there a way to do the same think without passing self as an input argument to do_something method in order to retrieve check method ?
# User code
module M
def do_something
called_from_object = ???
puts "Called from #{called_from_object}"
called_from_object.check
end
module_function :do_something
end
# Application code
class Bar
def check
puts "Checking from #{self}..."
end
end
class Foo < Bar
def do_stuff(scope, method)
scope.send methood
end
end
# Executed by user
Foo.new.do_stuff M, :do_something
Thanks for your support!
Came across this post while looking for an answer for my own purposes.
Didn't find one that was appropriate, so I dug through the Ruby source and put together an extension. I've bundled it as a gem- should install without any problem so long as you are using Ruby 1.9.1:
sudo gem install sender
This will not work with Ruby 1.8, as 1.8 has a different model for tracking frames.
http://rubygems.org/gems/sender
Not what you're asking for, but if Foo were to include M would that allow you do achieve what you're after? e.g.
module M
def do_something
puts "I am going to use the test method from the including class"
test
end
end
class Foo
include M
def test
puts "In Foo's test method"
end
def do_stuff
do_something
end
end
and then you can do:
irb(main):019:0> Foo.new.do_stuff
I am going to use the test method from the including class
In Foo's test method
If the idea is to have a module provide some general functionality and have the specifics in a class then this is a fairly common pattern in ruby, e.g. the Comparable module requiring the including class to implement <=>.
EDIT: I slightly changed the spec, to better match what I imagined this to do.
Well, I don't really want to fake C# attributes, I want to one-up-them and support AOP as well.
Given the program:
class Object
def Object.profile
# magic code here
end
end
class Foo
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Foo.new.bar("test")
Foo.new.barbar("test")
puts Foo.get_comment(:snafu)
Desired output:
Foo.bar was called with param: b = "test"
test
Foo.bar call finished, duration was 1ms
test
This really should be fixed
Is there any way to achieve this?
I have a somewhat different approach:
class Object
def self.profile(method_name)
return_value = nil
time = Benchmark.measure do
return_value = yield
end
puts "#{method_name} finished in #{time.real}"
return_value
end
end
require "benchmark"
module Profiler
def method_added(name)
profile_method(name) if #method_profiled
super
end
def profile_method(method_name)
#method_profiled = nil
alias_method "unprofiled_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
name = "\#{self.class}##{method_name}"
msg = "\#{name} was called with \#{args.inspect}"
msg << " and a block" if block_given?
puts msg
Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
end
ruby_eval
end
def profile
#method_profiled = true
end
end
module Comment
def method_added(name)
comment_method(name) if #method_commented
super
end
def comment_method(method_name)
comment = #method_commented
#method_commented = nil
alias_method "uncommented_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
puts #{comment.inspect}
uncommented_#{method_name}(*args, &blk)
end
ruby_eval
end
def comment(text)
#method_commented = text
end
end
class Foo
extend Profiler
extend Comment
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
A few points about this solution:
I provided the additional methods via modules which could be extended into new classes as needed. This avoids polluting the global namespace for all modules.
I avoided using alias_method, since module includes allow AOP-style extensions (in this case, for method_added) without the need for aliasing.
I chose to use class_eval rather than define_method to define the new method in order to be able to support methods that take blocks. This also necessitated the use of alias_method.
Because I chose to support blocks, I also added a bit of text to the output in case the method takes a block.
There are ways to get the actual parameter names, which would be closer to your original output, but they don't really fit in a response here. You can check out merb-action-args, where we wrote some code that required getting the actual parameter names. It works in JRuby, Ruby 1.8.x, Ruby 1.9.1 (with a gem), and Ruby 1.9 trunk (natively).
The basic technique here is to store a class instance variable when profile or comment is called, which is then applied when a method is added. As in the previous solution, the method_added hook is used to track when the new method is added, but instead of removing the hook each time, the hook checks for an instance variable. The instance variable is removed after the AOP is applied, so it only applies once. If this same technique was used multiple time, it could be further abstracted.
In general, I tried to stick as close to your "spec" as possible, which is why I included the Object.profile snippet instead of implementing it inline.
Great question. This is my quick attempt at an implementation (I did not try to optimise the code). I took the liberty of adding the profile method to the
Module class. In this way it will be available in every class and module definition. It would be even better
to extract it into a module and mix it into the class Module whenever you need it.
I also didn't know if the point was to make the profile method behave like Ruby's public/protected/private keywords,
but I implemented it like that anyway. All methods defined after calling profile are profiled, until noprofile is called.
class Module
def profile
require "benchmark"
#profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless #profiled_methods.include?(method)
#profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
You can now use it as follows:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
Call the methods as you would normally:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
And get benchmarked output (and the result of the original method_added callback just to show that it still works):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
One thing to note is that it is impossible to dynamically get the name of the arguments with Ruby meta-programming.
You'd have to parse the original Ruby file, which is certainly possible but a little more complex. See the parse_tree and ruby_parser
gems for details.
A fun improvement would be to be able to define this kind of behaviour with a class method in the Module class. It would be cool to be able to do something like:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
I'll leave this meta-meta-programming exercise for another time. :-)
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.
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 !#listeners.has_key?(name)
#listeners[name].delete_if { |o| o == func }
end
def trigger_event(name, *args)
return if !#listeners.has_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")
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 ;-)
The Observable module?
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).
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.
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!
A quick note on this. I suggest you look at EventMachine
https://rubygems.org/gems/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.
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
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