Ruby blocks rock. They're so great, sometimes we want to pass multiple blocks to a method, but, Ruby doesn't allow this. Consider the classic example where we want to perform a block of code if an action succeeds and perform different code if an action fails.
Fortunately, Ruby provides us the ability to modify itself. So with a couple lines of code we can achieve the above situation. In this case, let's add a perform
method to the Proc class. This method will simply yield an anonymous Class with methods defined to handle our blocks.
class Proc
def perform(callable)
self === Class.new do
method_name = callable.to_sym
define_method(method_name) { |&block| block.nil? ? true : block.call }
define_method("#{method_name}?") { true }
def method_missing(method_name, *args, &block) false; end
end.new
end
end
That's it!
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)
if Twitter.update(message)
block.perform :success
else
block.perform :failure
end
end
We call perform
on the block and give it a name. Any name will work :success, :error, :fail!, etc. 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
puts "Sorry, something went wrong."
end
end
Output:
Tweet successful!
The nice thing here is that we can define our own DSL. We don't need to worry about making sure passing too many or unexpected blocks. We could have easily said where.success
or on.error
or update.fail!
.
Bonus: In addition to wrapping code in blocks, our Proc#perform
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
I like the calling semantics a lot. Though I'm not a fan of monkey patching Ruby.