Blocos com argumentos opcionais no Ruby 1.8.x
O Ruby 1.9 tem umas coisas novas bem legais. Uma delas é a habilidade de definir valores padrão para os argumentos passados para um bloco de código, como no exemplo abaixo:
pow = proc { |a, b = 2| a**b } pow.call 3, 3 # 27 pow.call 3 # 9
Isso é bem útil, por exemplo, quando criamos um método dinâmicamente com metaprogramação e queremos que algum argumento desse método seja opcional.
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
Mas no Ruby 1.8.x não temos isso, não podemos definir valores padrão para os argumentos de um bloco. Ok, sua aplicação roda com Ruby 1.8.x e você tem que criar métodos dinâmicamente e estes métodos devem ter parâmetros opcionais. Existe uma solução, não tão elegante quanto como ocorre no Ruby 1.9 mas bastante funcional e simples.
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
Ao passarmos um splat (aquele carinha com um asterisco à esquerda) como argumento para o bloco, o que quer que chegue ali será tratado como um array. Dessa forma, podemos simular a existência de valores padrão para os argumentos. Podemos na verdade passar qualquer quantidade de argumentos e definir os valores padrão conforme desejarmos.
O que se perde, infelizmente, é a capacidade de validar a quantidade de argumentos que o método deve ser receber quando for executado. Mas isso pode ser contornado validando-se os elementos do array recebido pelo bloco.
Eu costumo usar essa técnica para criar métodos que podem ou não receber um hash com opções, o que se mostra útil por exemplo em plugins ou gems, como por exemplo:
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 # funciona obj.magic_method :abra => "cadabra" # também funciona!
Muito legal a dica, vou passar a usá-la. Valeu Cássio.
sou fã dos hook methods! =)