How to implement a "callback" in Ruby?

I'm not sure about the best idiom for C-style callbacks in Ruby - or if something is even better (and less like C). In C, I would do something like:

void DoStuff( int parameter, CallbackPtr callback ) { // Do stuff ... // Notify we're done callback( status_code ) } 

What is a good Ruby equivalent? Essentially, I want to call the method of the past in the class when a certain condition is met in "DoStuff"

+62
ruby callback
Nov 05 '09 at 1:52
source share
6 answers

A ruby ​​equivalent that is not idiomatic will be:

 def my_callback(a, b, c, status_code) puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}" end def do_stuff(a, b, c, callback) sum = a + b + c callback.call(a, b, c, sum) end def main a = 1 b = 2 c = 3 do_stuff(a, b, c, method(:my_callback)) end 

An idiomatic approach would be to pass a block instead of a method reference. One advantage that a block has over an autonomous method is the context - the closure block, so it can refer to variables from the scope in which it was declared. This reduces the number of do_stuff parameters needed to go to the callback. For example:

 def do_stuff(a, b, c, &block) sum = a + b + c yield sum end def main a = 1 b = 2 c = 3 do_stuff(a, b, c) { |status_code| puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}" } end 
+75
Nov 05 '09 at 4:06
source share

This “idiomatic block” is a very important part of everyday Ruby and is often covered in books and textbooks. The Ruby information section contains links to useful [online] learning resources.




The idiomatic way is to use the block:

 def x(z) yield z # perhaps used in conjunction with #block_given? end x(3) {|y| y*y} # => 9 

Or maybe converted to Proc ; here I show that a “block” converted to Proc implicitly with &block is just another “invoked” value:

 def x(z, &block) callback = block callback.call(z) end # look familiar? x(4) {|y| y * y} # => 16 

(Use only the form above to save the-now-Proc block for later use or in other special cases, as it adds overhead and syntax noise.)

However, lambda can be used just as easily (but this is not idiomatic):

 def x(z,fn) fn.call(z) end # just use a lambda (closure) x(5, lambda {|y| y * y}) # => 25 

While the above approaches can all wrap up a “method call” because they create closures, bound Methods can also be considered first-class invoked objects:

 class A def b(z) z*z end end callable = A.new.method(:b) callable.call(6) # => 36 # and since it just a value... def x(z,fn) fn.call(z) end x(7, callable) # => 49 

In addition, it is sometimes useful to use the #send method (in particular if the method is known by name). Here it saves the intermediate object of the method that was created in the last example; Ruby is a messaging system:

 # Using A from previous def x(z, a): a.__send__(:b, z) end x(8, A.new) # => 64 

Happy coding!

+70
Nov 05 '09 at 2:27
source share

I studied the topic a bit more and updated the code.

The next version is an attempt to generalize the technique, although it remains extremely simplified and incomplete.

I pretty much stole - came up, found inspiration in - implementing DataMapper callbacks, which seems to me quite complete and beautiful.

I highly recommend taking a look at the code @ http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb

In any case, the attempt to reproduce functionality using the Observable module was quite attractive and instructive. A few notes:

  • seems to be required because the original instance methods are not available at the time the callbacks are registered
  • inclusion class is made both observable and self-serving
  • the example is limited to instance methods, does not support blocks, arguments, etc.

the code:

 require 'observer' module SuperSimpleCallbacks include Observable def self.included(klass) klass.extend ClassMethods klass.initialize_included_features end # the observed is made also observer def initialize add_observer(self) end # TODO: dry def update(method_name, callback_type) # hook for the observer case callback_type when :before then self.class.callbacks[:before][method_name.to_sym].each{|callback| send callback} when :after then self.class.callbacks[:after][method_name.to_sym].each{|callback| send callback} end end module ClassMethods def initialize_included_features @callbacks = Hash.new @callbacks[:before] = Hash.new{|h,k| h[k] = []} @callbacks[:after] = @callbacks[:before].clone class << self attr_accessor :callbacks end end def method_added(method) redefine_method(method) if is_a_callback?(method) end def is_a_callback?(method) registered_methods.include?(method) end def registered_methods callbacks.values.map(&:keys).flatten.uniq end def store_callbacks(type, method_name, *callback_methods) callbacks[type.to_sym][method_name.to_sym] += callback_methods.flatten.map(&:to_sym) end def before(original_method, *callbacks) store_callbacks(:before, original_method, *callbacks) end def after(original_method, *callbacks) store_callbacks(:after, original_method, *callbacks) end def objectify_and_remove_method(method) if method_defined?(method.to_sym) original = instance_method(method.to_sym) remove_method(method.to_sym) original else nil end end def redefine_method(original_method) original = objectify_and_remove_method(original_method) mod = Module.new mod.class_eval do define_method(original_method.to_sym) do changed; notify_observers(original_method, :before) original.bind(self).call if original changed; notify_observers(original_method, :after) end end include mod end end end class MyObservedHouse include SuperSimpleCallbacks before :party, [:walk_dinosaure, :prepare, :just_idle] after :party, [:just_idle, :keep_house, :walk_dinosaure] before :home_office, [:just_idle, :prepare, :just_idle] after :home_office, [:just_idle, :walk_dinosaure, :just_idle] before :second_level, [:party] def home_office puts "learning and working with ruby...".upcase end def party puts "having party...".upcase end def just_idle puts "...." end def prepare puts "preparing snacks..." end def keep_house puts "house keeping..." end def walk_dinosaure puts "walking the dinosaure..." end def second_level puts "second level..." end end MyObservedHouse.new.tap do |house| puts "-------------------------" puts "-- about calling party --" puts "-------------------------" house.party puts "-------------------------------" puts "-- about calling home_office --" puts "-------------------------------" house.home_office puts "--------------------------------" puts "-- about calling second_level --" puts "--------------------------------" house.second_level end # => ... # ------------------------- # -- about calling party -- # ------------------------- # walking the dinosaure... # preparing snacks... # .... # HAVING PARTY... # .... # house keeping... # walking the dinosaure... # ------------------------------- # -- about calling home_office -- # ------------------------------- # .... # preparing snacks... # .... # LEARNING AND WORKING WITH RUBY... # .... # walking the dinosaure... # .... # -------------------------------- # -- about calling second_level -- # -------------------------------- # walking the dinosaure... # preparing snacks... # .... # HAVING PARTY... # .... # house keeping... # walking the dinosaure... # second level... 

This simple presentation of using Observable may be helpful: http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html

+5
Sep 03 '10 at 19:03
source share

So, it can be very “ruby” and I am not a “professional” Ruby developer, so if you guys pretend to be careful be careful :)

Ruby has a built-in module called Observer. I did not find it easy to use, but to be honest, I did not give it a big chance. In my projects, I resorted to creating my own EventHandler type (yes, I use C # a lot). Here is the basic structure:

 class EventHandler def initialize @client_map = {} end def add_listener(id, func) (@client_map[id.hash] ||= []) << func end def remove_listener(id) return @client_map.delete(id.hash) end def alert_listeners(*args) @client_map.each_value { |v| v.each { |func| func.call(*args) } } end end 

So, to use this, I show it as a member of a read-only class:

 class Foo attr_reader :some_value_changed def initialize @some_value_changed = EventHandler.new end end 

Clients of the Foo class can subscribe to this event:

 foo.some_value_changed.add_listener(self, lambda { some_func }) 



I'm sure this is not an idiomatic Ruby, and I'm just relearning my C # experience into a new language, but it worked for me.

+3
Nov 05 '09 at 2:33
source share

I often implement callbacks in Ruby, as in the following example. It is very convenient to use.

 class Foo # Declare a callback. def initialize callback( :on_die_cast ) end # Do some stuff. # The callback event :on_die_cast is triggered. # The variable "die" is passed to the callback block. def run while( true ) die = 1 + rand( 6 ) on_die_cast( die ) sleep( die ) end end # A method to define callback methods. # When the latter is called with a block, it saved into a instance variable. # Else a saved code block is executed. def callback( *names ) names.each do |name| eval <<-EOF @#{name} = false def #{name}( *args, &block ) if( block ) @#{name} = block elsif( @#{name} ) @#{name}.call( *args ) end end EOF end end end foo = Foo.new # What should be done when the callback event is triggered? foo.on_die_cast do |number| puts( number ) end foo.run 
0
Aug 14 '10 at 15:13
source share

I know this is an old post, but others who come across this may find a useful solution.

http://chrisshepherddev.blogspot.com/2015/02/callbacks-in-pure-ruby-prepend-over.html

0
Mar 10 '15 at 9:23
source share



All Articles