Code blocks with optional arguments in Ruby 1.8.x

Ruby 1.9 has some cool new features. One of them is the ability to define default values for the arguments passed to code blocks, like in the following example:

pow = proc { |a, b = 2| a**b }

pow.call 3, 3
# 27

pow.call 3
# 9

This is very useful, for instance, when we dynamically create new methods using metaprogramming and want some of the arguments for these methods to be optional.

class MyMath
  class < < self
    define_method :pow do |base, exponent = 2|
      base**exponent
    end
  end
end

MyMath.pow 3 # 9
MyMath.pow 2, 3 # 8

But in Ruby 1.8.x we can't do that, we can't define default values the arguments of a code block. So what if your application runs on 1.8.x and you need to dynamically create methods with optional arguments? There is a solution, not so elegant as the Ruby 1.9 approach but still very functional and simple:

class MyMath
  class < < self
    define_method :pow do |*args|
      base, exponent = args[0], args[1] || 2
      base**exponent
    end
  end
end

MyMath.pow 3 # 9
MyMath.pow 2, 3 # 8

When we pass a splat (that thing with an asterisk on the left) as the argument to the block, whatever gets there will be treated as an array. This way, we can simulate the existence of default values for those arguments. We can actually pass any number of arguments and define the default values in any way we want.

What is lost, unfortunately, is the ability to validate the number of arguments that the method should receive when called. But we can do that validating the elements inside the array that the block receives.

I usually use this technique to create methods that can optionally receive an options hash, what is very useful when writing plugins or gems, for instance:

module MyModule
  module ClassMethods
    def do_the_magic
      class_eval do
        define_method :magic_method do |*args|
          options = args.first || {}
          # ... 
        end
      end
    end
  end

  def self.included(base)
    base.extend ClassMethods
  end
end

class MyClass
  include MyModule
  do_the_magic
end

obj = MyClass.new
obj.magic_method # works
obj.magic_method :abra => "cadabra" # also works!