nele. Quando fazemos include de um módulo dentro de uma classe, seus métodos
são misturados, e portanto temos acesso como se o método estivesse implementado
diretamente na classe. O mesmo acontece com os métodos do módulo. Como a veri-
ficação da existência de métodos é apenas em tempo de execução, este contrato de
implementação não é verificado pelo interpretador, simplificando, portanto, a im-
plementação.
Uma característica importante é que, quando fazemos um mixin, o módulo acaba
se tornando um ancestral da classe que inclui este módulo. Isso nos dá uma série de
62
Casa do Código
Capítulo 2. Conhecendo Ruby
vantagens, tal como a verificação de tipos:
shipping.is_a? Shipping # true
O mesmo pode ser feito com módulos como Enumerable. Esta é uma forma de
garantir que um objeto responde a uma série de métodos de maneira uniforme.
Mixins com extend
Existe uma outra forma de fazer mixins, com a palavra-chave extend. A dife-
rença é que os métodos são incluídos a nível de classe, e não mais de instância:
module Builder
def build(attributes={})
new_object = new
attributes.each do |name, value|
# O código abaixo é o mesmo que
# new_object.name = value
new_object.send "#{name}=", value
end
new_object
end
end
class ShippingPrice
extend Builder
attr_accessor :width, :height, :depth
end
shipping = ShippingPrice.build({
:width => 0.8,
:height => 0.2,
:depth => 0.3
})
shipping.width # 0.8
O código anterior usa um pouco do que chamamos em Ruby de meta-
programação, ou seja, programação para gerar código. O módulo Builder basica-
mente cria um construtor em que um Hash é convertido para atributos de um objeto
63
2.6. Classes e módulos
Casa do Código
via métodos de escrita (métodos terminados com =). Usamos então o extend para
misturar a funcionalidade de construção ao nível da classe.
Meta-programação
Meta-programação é uma funcionalidade importante para quem escreve
bibliotecas e sistemas avançados. Como este é um livro para quem está
começando, meta-programação pode dar um nó na cabeça e portanto
não vamos ver muito mais detalhes sobre o assunto.
Porém, se você se sente um programador aventureiro, recomendo a lei-
tura do Metaprogramming Ruby [8], um livro excelente para quem quer
iniciar nas artes às vezes misteriosas da meta-programação.
Exceções
O controle de exceções em Ruby acontece em 3 fases. Primeiro, um bloco de
código, iniciado pela cláusula begin é executado. Em seguida, caso alguma exceção
aconteça (via erros de sintaxe Ruby ou pela cláusula raise), o interpretador Ruby vai
buscar em todas as possíveis cláusulas rescue qual é pertinente para o tratamento
daquela exceção. Isso é feito através da comparação de classes, ou sua hierarquia. E,
por fim, se existir, o bloco ensure é executado, independente da ocorrência ou não
de uma exceção.
Sim, muita coisa pode acontecer em um pedaço pequeno de código, então va-
mos com calma para entender cada passo. Primeiro, vejamos como tratar exceções
genericamente.
def calculate_installment_price(total_value, installments)
begin
puts "O resultado é #{total_value / installments}"
rescue
puts "Não foi possível calcular o valor da parcela"
end
end
calculate_installment_price(100, 5) # O resultado é 20.0
64
Casa do Código
Capítulo 2. Conhecendo Ruby
calculate_installment_price(100, 0) # Não foi possível calcular
# o valor da parcela
Neste caso, quando passamos um valor inválido para o número de parcelas (ins-
tallments), uma exceção é disparada, e a execução é interrompida, levando a exe-
cução do programa para o bloco associado ao rescue. Por essa razão, a impressão
do texto "O resultado é ... não é executada. É importante notar que nesse caso não
importa qual exceção seja disparada, o bloco rescue será executado.
É possível, porém, associar um bloco rescue diretamente a uma classe. Dessa
forma, dependendo do erro que ocorrer no bloco associado, podemos tratá-lo de
maneira diferente. Vamos usar essa ideia para mostrar um erro diferente para o
usuário:
def calculate_installment_price(total_value, installments)
begin
puts "O resultado é #{total_value / installments}"
rescue ZeroDivisionError
puts "Número de parcelas deve ser > 0"
rescue
puts "Não foi possível calcular o valor da parcela"
end
end
calculate_installment_price(100, 5) # O resultado é 20.0
calculate_installment_price(100, 0) # Número de parcelas
# deve ser > 0
calculate_installment_price("", 0) # Não foi possível calcular
# o valor da parcela
Dessa forma, quando enviamos 0 como número de parcelas, a exceção ge-
rada pelo interpretador é a ZeroDivisionError, que é tratada pelo primeiro bloco
rescue. No segundo caso, porém, este bloco não corresponde à exceção gerada
(NoMethodError), portanto o último bloco é executado.
É bastante comum termos que garantir que algo aconteça mesmo que haja erro
durante um processo, como liberar recursos. Por isso, temos o bloco ensure. O bloco
ensure sempre será executado, independente da ocorrência de exceções. Inclusive,