Pular para o conteúdo
janeiro 12, 2010 / cassiomarques

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!

2 Comentários

Deixe um comentário
  1. Junio Vitorino / jan 12 2010 4:36 pm

    Muito legal a dica, vou passar a usá-la. Valeu Cássio.

  2. Jônatas Davi Paganini / jan 19 2010 1:31 pm

    sou fã dos hook methods! =)

Deixe um comentário