Ruby Blocks as Dynamic Callbacks
Callbacks are a great technique for achieving simplicity and flexibility. Simply put, a callback is a block of code passed as an argument to a method. In Ruby, code blocks are everywhere and Ruby makes it trivial to pass a block of code to methods. For example:
def foo(bar, &block)
callback = block
callback.call(bar)
end
foo(5) {|x| x * x} # => 25
But what do we do when a method needs two blocks of code or more? Consider the classic case where we want a method to execute a block of code if an action succeeds or call different code if an action fails.
In this article, I will demonstrate how we can pass multiple blocks to a method and with some metaprogramming, we can achieve a dynamic callback mechanism with just a few lines of code.
Let’s add a method called callback
to the Proc class:
class Proc
def callback(callable, *args)
self === Class.new do
method_name = callable.to_sym
define_method(method_name) { |&block| block.nil? ? true : block.call(*args) }
define_method("#{method_name}?") { true }
def method_missing(method_name, *args, &block) false; end
end.new
end
end
That’s it! The above Proc#callback
method simply yields an anonymous class
with methods defined to handle our callbacks. This allows for the capability of
creating and storing dynamic callbacks, which can later be looked up and
executed as needed.
Notice anything unusual? We’re using the ===
operand to invoke the
block. Proc#===
is an alias for Proc.call
. Anything on the right side of
===
acts as the proc’s parameter. Normally, this is to allow a proc object to
be a target of a when
clause in case statements, but we’re using it as a super
simple way of invoking our anonymous class.
Let’s try it with something useful. Let’s say we’re writing something which needs to happen in an all-or-nothing, atomic fashion. Either the whole thing works, or none of it does. A simple case is tweeting:
def tweet(message, &block)
Twitter.update(message)
block.callback :success
rescue => e
block.callback :failure, e.message
end
The tweet
method accepts a message string and &block parameters. We call
callback
on the block and give it a name. Any name will work :success, :error,
:fail!, whatever. In addition, we can pass arguments to the blocks (more on that
later). Now we can provide a status if the tweet was successful or not:
tweet "Ruby methods with multiple blocks. #lolruby" do |on|
on.success do
puts "Tweet successful!"
end
on.failure do |status|
puts "Error: #{status}"
end
end
The advantage here is that we define our own mini DSL. We don’t need to worry
about passing too many or unexpected blocks. We could have easily said
where.success
or on.error
or update.fail!
. Also note the on.failure
block includes a status
parameter - this contains the exception message
captured in the tweet
method above. So if Twitter was down for whatever
reason, the on.failure
block would be invoked and printed ‘Error: Twitter is
down or being upgraded’.
Bonus: In addition to wrapping code in blocks, our Proc#callback
method
defines boolean style methods. So we could have call the tweet method like this
if we wanted to:
tweet "Ruby methods with multiple blocks. #lolruby" do |update|
puts "Tweet successful!" if update.success?
puts "Sorry, something went wrong." if update.failure?
end
Put the Proc#callback
method in a utility library and your code will look neat and tidy.
As always, I welcome your thoughts and feedback. Let me know what you think of the techniques shown here, or share your own favorite code block tricks.