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
Related
There has got to be a more efficient way to do this in Ruby. I have a list of methods that scrape the same things (title, price) across multiple sites but in slightly different ways based on the code in each store. For example:
def store1_get_title
def store1_get_price
def store2_get_title
def store2_get_price
def store3_get_title
def store3_get_price
When calling all of these functions, I would just like a generic call with say a "namespace" parameter to do invoke any of these methods without having to type out all of them, something like:
for get_all_stores().each do |store|
store::get_title
store::get_price
end
...which would invoke store1_get_title, store1_get_price, store2_get_title, store2_get_price like I want. Is there something like this or a better way to do this?
Hope that makes sense. Thanks for any input!
Edit: these tasks are in rake task code.
This is a perfect use for classes. If you find two stores with the same software powering them (maybe Yahoo commerce or EBay stores) you can make instances of the classes with different parameters.
class Amazon
def get_price; end
def get_title; end
end
class Ebay
def initialize seller; end
def get_price; end
def get_title; end
end
[Amazon.new, Ebay.new("seller1"), Ebay.new("seller2")] each do |store|
store.get_price
store.get_title
end
And you can do this in any other object-oriented language by defining a base class or interface that all of the stores implement/inherit.
I don't understand the logic of your application. Perhaps you should think about a class definition (see Ken Blooms answer).
Nevertheless you could try a dynamic call with send:
def store1_get_title
p __method__
end
def store1_get_price
p __method__
end
def store2_get_title
p __method__
end
def store2_get_price
p __method__
end
def store3_get_title
p __method__
end
def store3_get_price
p __method__
end
all_stores = ['store1', 'store2', 'store3']
all_stores.each do |store|
send("#{store}_get_title")
send("#{store}_get_price")
end
You didn't define what get_all_stores returns. In my example I used Strings. You could add some syntactical sugar and extend String (I don't recommend this)
class String
def get_title()
send("#{self}_get_title")
end
def get_price()
send("#{self}_get_price")
end
end
all_stores.each do |store|
store.get_title
store.get_price
end
One last remark. You wrote
for get_all_stores().each do |store|
each alone should be enough. for is not ruby-like and in combination with each it doen't look reasonable to me.
Is there a quick way to track the methods that are being called on an object? Often, when I'm working with a gem at a level just below their public interface, I run into errors that are hard to track down. Ultimately, I end up tracking the object through the source code and keeping everything in my head.
But it would be nice to be able to call something like a #log_method_calls on an object so that, say, all methods called on it get printed to stdout or something. Is there any way to accomplish this?
There are several methods to do it, depending on the situation.
If it' possible to create a new object instead of the observed, you can easily write an observer class using method_missing.
class LogProxy
def initialize obj
#obj = obj
end
def method_missing(name, *args)
puts "#{name} => #{args.to_s}"
#desk.send(name, *args)
end
end
If it's not possible, you still may use alias_method. It's a bit more tricky, but using Module.instance_methods you can chain every method of anything.
Something like:
module Logger
def self.included(mod)
mod.instance_methods.each do |m|
next if m =~ /with_logging/
next if m =~ /without_logging/
mod.class_eval do
define_method "#{m}_with_logging" do |*args|
puts "#{m} called #{args.to_s}"
self.send_without_logging "#{m}_without_logging", *args
end
alias_method "#{m}_without_logging", m
alias_method m, "#{m}_with_logging"
end
end
end
end
TargetClass.send(:include, Logger)
i want to be able to do event handling with ruby. I know there is no native way to do this, but i found a solution here in stackoverflow:
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
The problem is this:
1- It seems when you add a method to the listen array it executes right away
2- When the event triggers, it throws a NoMethodError: undefined method call for nil:NilClass
I am new to ruby so i dont understand the code completly, i feel its missing some pieces of code lol... (mostly because i dont know all ruby syntax)
thanks
Some questions:
- What means &p ?
- What is ||=?
- what means <
I think you may be trying to reinvent the wheel here. I would recommend using Observable instead. It's in the standard library, just require "observer" and include the Observable module into your class.
If you want to do event handling in a small Ruby script (ie: NOT a web application), then I recommend using the Unobservable gem (as in: it's NOT the Observable gem, har har har). You can find some basic details / links about the gem here:
https://briandamaged.org/blog/?p=1074
https://briandamaged.org/blog/?p=1161
This gem makes it easy to define multiple events in a single object. For example:
require 'unobservable'
class Button
include Unobservable::Support
attr_event :clicked
attr_event :double_clicked
def click(x, y)
raise_event(:clicked, x, y)
end
def double_click(x, y)
raise_event(:double_clicked, x, y)
end
end
button = Button.new
button.clicked.register {|x, y| puts "You just clicked: #{x} #{y}"}
button.click(2, 3)
Lol, ok i solved the problem... i wasnt calling listen_event correctly...
it should be
listen_event(:indexChanged,method(:sayIndex))
not
listen_event(:indexChanged,sayIndex(:index))
still learnin the ropes on ruby lol
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. :-)
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.