Sei sulla pagina 1di 70

Ruby Metaprogramming

por Nando Vieira

Copyright Hellobits & Nando Vieira. Todos os direitos reservados.


Nenhuma parte desta publicao pode ser reproduzida sem o consentimento dos autores.
Todas as marcas registradas so de propriedade de seus respectivos donos.
Ruby Metaprogramming, Nando Vieira, 2a verso
Veja outros e-books e screencasts publicados pela Hellobits no site do HOWTO

Contedo
1

Introduo

32 Blocos

Entendendo o self

34 Mtodos

Variveis de instncia

37

Constantes

Entendendo classes Singleton

40

Hooks

Classes

40 Mdulos e Classes

11

Mdulos & Mixins

43 Mtodos

14

Mtodos

23

Aprenda com exemplos

15 Executando mtodos dinamicamente

50 Registrando classes

17 Definindo novos mtodos

54 method_missing e definio de mtodos

19 Redefinindo mtodos existentes

55 Definindo macros

20 Removendo mtodos

59 Criando DSLs

Execuo de cdigo
23 Module#class_eval
25 Object#instance_eval
27 Kernel#eval

30

50

Callable Objects
30 Procs e lambdas

66 E para finalizar

Captulo 1

Introduo
Poucas coisas no Ruby so to pouco entendidas como a metaprogramao. Embora seja uma tcnica extremamente
til, ela exige muito mais conhecimento da linguagem como um todo, que pode levar um certo tempo at que seja
adquirido.
A metaprogramao faz parte do Ruby. Isso to verdade que voc no consegue distinguir trechos de um cdigo
Ruby e afirmar se aquilo ou no metaprogramao. A metaprogramao faz parte do dia-a-dia do desenvolvedor
Ruby. Ou pelo menos deveria.
Infelizmente, muitos consideram a metaprogramao como pura magia negra, muito mais pelo fato de no
entenderem certos aspectos da linguagem, do que pelo modo como o Ruby executa tal tcnica.

No existe magia negra. Apenas Ruby.


O objetivo deste livro ser um guia com tcnicas utilizadas por desenvolvedores Ruby de todo o mundo, com
exemplos prticos de como criar DSLs, executar e definir mtodos dinamicamente, dentre muitos outros exemplos.
Nando Vieira, Janeiro de 2011

Captulo 1

Entendendo o self
No Ruby, o self ser sempre uma referncia ao receiver atual e pode ser diferente dependendo do contexto em que
voc estiver. Por exemplo, quando estamos no namespace global, nosso self ser o objeto main. J em uma classe,
nosso self ser a prpria classe.
puts self
#=> main
class Thing
puts self
end
#=> Thing

Toda vez que voc executa um mtodo, o Ruby ir verifica se ele existe no receiver padro, self, a menos que voc
especifique explicitamente quem ser o receiver.
class Thing
def do_something
puts "doing something"
end
def do_something_else
do_something
end
end

No mtodo Thing#do_something_else poderamos usar self.do_something, mas isso faria com que nosso cdigo
apenas ficasse mais poludo. No entanto, definir o receiver uma coisa muito comum e que, voc pode at no se dar
conta, mas o faz constantemente quando escreve cdigo Ruby.

numbers = [3,1,2]
numbers.sort
#=> [1,2,3]

Na prtica, o receiver especificado antes do ponto na chamada de mtodos, como em numbers.sort.

Variveis de instncia
Alm de ser o receiver padro, self tambm o responsvel por armazenar variveis de instncia de um objeto. Veja o
exemplo abaixo.
class Person
def initialize
initialize(name)
@name = name
end
def name
@name
end
end
john = Person
Person.new("John Doe")
john.name
#=> "John Doe"

A instncia da classe Person possui uma nica varivel de instncia associada ao seu objeto (self) que retornada pelo
mtodo Person#name. Analogamente, podemos definir variveis de instncia em qualquer objeto, como classes.
class Person
def self.count
count
@count ||= 0
end
def self.count=
count=(increment)

@count = increment
end
def initialize
initialize(name)
@name = name
self.class.count += 1
end
def name
@name
end
end
john = Person
Person.new("John Doe")
Person
Person.count
#=> 1

O exemplo acima mostra como variveis de instncia podem ser usadas em contextos diferentes. Primeiro, estamos
definindo um contador de instncias da classe Person, cujo valor ser armazenado em @count. Depois, em nossa
prpria instncia, definimos o nome com a varivel @name.

Captulo 1

Entendendo classes Singleton


O nome Singleton usado pelo Ruby nada tem a ver com o Singleton Pattern, que tambm est disponvel com a
biblioteca Singleton.
Todo objeto do Ruby est associado a duas classes: a classe que a instanciou e uma classe annima, escondida,
especfica do objeto. Esta classe annima chamada de Singleton Class, mas antes de ter um nome oficial tambm era
chamada de anonymous class, metaclass, eigenclass ou ghost class.
A sintaxe mais comum para acessar a classe Singleton
class << object
end

onde object o objeto cuja classe Singleton voc quer. muito comum vermos algo como o exemplo seguir para
definir mtodos em uma classe.
class Person
class << self
def count
@count ||= 0
end
end
end

Aqui, estamos definindo um mtodo na classe Singleton do objeto Person (lembre-se: tudo no Ruby objeto, inclusive
classes). Como consequncia, isso ir definir o mtodo Person.count. O efeito exatamente o mesmo que
class Person
def self.count
count

@count ||= 0
end
end

No Ruby 1.9.2, foi adicionado o mtodo Object#singleton_class, que apenas um atalho para a sintaxe class <<
self; self; end. Em verses mais antigas, voc pode injetar este mtodo com um cdigo como este
class Object
def singleton_class
class << self
self; self; end
end unless respond_to?(:singleton_class)
end

Quando voc cria uma classe Singleton em um objeto (uma instncia), no poder mais utilizar o mtodo
Marshal.dump, j que a biblioteca Marshal no suporta objetos com classes Singleton (ela ir lanar a exceo
TypeError: singleton can't be dumped). A nica maneira de fazer isso e ainda poder utilizar o Marshal
utilizando o mtodo Object#extend.
Toda vez que injeta mtodos em um objeto, eles so adicionados como mtodos singleton. O que realmente
importante saber que estes mtodos pertecem unicamente ao objeto em que foram definidos, no afetando nenhum
outro objeto da hierarquia.
string = "Hi there!"
another_string = "Hi there!"
def string.to_yo
to_yo
self.gsub(/\b(Hi|Hello)( there)\b?!?/, "Yo! Wassup?")
end
string.to_yo
#=> "Yo! Wassup?"
another_string.respond_to?(:to_yo)
#=> false

E para provar que o mtodo string#to_yo singleton, podemos utilizar o mtodo Object#singleton_methods.
string.singleton_methods
#=> ["to_yo"]
another_string.singleton_methods
#=> []

Agora, sabendo que voc pode adicionar mtodos em um objeto com uma sintaxe como def object.some_method;
end, perceba que exatamente isso que fazemos quando definimos um mtodo em uma classe; a nica diferena
que passamos o prprio self.
class Person
def self.say_hello
say_hello
"Hello there!"
end
end
Person
Person.singleton_methods
#=> ["say_hello"]

Com base nesse exemplo, possvel afirmar que mtodos de classe no existem no Ruby! Pelo menos no no
sentido de mtodos estticos! O que acontece que estes mtodos pertencem a um objeto, que por acaso uma
classe.

Captulo 1

Classes
No Ruby, classes e mdulos so como qualquer objeto. A palavra-chave defined? retorna informao sobre a
expresso que est recebendo.
class Person
end
defined?
defined?(Person
Person)
#=> "constant"

Como voc pode ver, classes nada mais so do que simples constantes. Embora normalmente usamos as palavraschave class e module para criarmos novas definies, voc pode atingir o mesmo resultado utilizando as classes Class
e Module.
cls = Class
Class.new
defined?
defined?(cls)
#=> "local-variable"
obj = cls.new
obj.instance_of?(cls)
#=> true

Uma coisa muito interessante de classes dinmicas que elas no possuem nome.
cls.name
#=> nil

Mas se classes dinmicas no possuem nome, muitas coisas baseadas nessa informao podem no funcionar. Na
verdade, o Ruby utiliza um truque muito interessante para definir qual o nome da classe.
cls = Class
Class.new
cls.name
#=> nil
MyClass = cls
MyClass
MyClass.name
#=> "MyClass"

O nome da classe inferido da constante ao qual ela foi atribuda. No entanto, este nome definido apenas quando
essa classe ainda no tem um nome.
cls = Class
Class.new
cls.name
#=> nil
MyClass = cls
MyClass
MyClass.name
#=> "MyClass"
OtherClass = cls
OtherClass
OtherClass.name
#=> "MyClass"

Se voc quiser, no precisa nunca mais utilizar a palavra-chave class para definir suas classes. Claro que seu cdigo
perder um pouco da elegncia, mas esta tcnica pode ser til.
Tambm possvel definir qual a superclasse de uma classe dinmica. Basta passar uma classe como argumento do
mtodo Class.new.
class Person
Person; end
Author = Class
Class.new(Person
Person)

Author
Author.ancestors
#=> [Author, Person, Object, Kernel, BasicObject]

Para definir mtodos desta classe dinmica basta voc passar um bloco, que ir ser o corpo de sua classe.
Person = Class
Class.new do
def self.say
say(message)
puts message
end
end
Person
Person.say "Hi there!"
#=> "Hi there!"

10

Captulo 1

Mdulos & Mixins


Conforme a popularidade do Ruby aumenta, a chance de diferentes bibliotecas terem classes com nomes iguais
tambm aumenta. Imagine que voc est desenvolvendo um aplicativo de lista de tarefas e tem uma classe chamada
Task, que salva os dados em banco de dados.
class Task < ActiveRecord
ActiveRecord::Base
Base; end

Se neste aplicativo voc fosse utilizar uma verso antiga do Rake, teria um problema de coliso de classes, j que ele
tambm implementava a classe Task. E como o Ruby possui classes abertas, voc nem mesmo ficaria sabendo ou iria
saber da pior maneira possvel.
A soluo para este problema foi bastante simples. Todos as classes que estavam no toplevel foram movidas para um
mdulo Rake1.
require "rake"
Rake
Rake.class
#=> Module
Rake
Rake::Task
Task.class
#=> Class

Alm de serem timos para definir namespaces, mdulos possuem uma outra utilidade. Eles permitem compartilhar
funcionalidade entre diversos objetos com um conceito chamado mixin, evitando a necessidade de mltiplas heranas
de classes2.
1

O Rake comeou a utilizar o mdulo Rake como namespace partir da verso 0.6.0.

Se voc quiser ir alm, voc nunca mais precisar usar herana de classes com mixins.
11

Imagine que alm da classe Array, voc tambm quer que o mtodo Range#sum seja definido. Em vez de duplicar o
cdigo deste mtodo, voc pode simplesmente criar um mdulo e inclu-lo nas classes Array e Range.
Primeiro, vamos extrair o mtodo Array#sum para o mdulo Summable.
module Summable
def sum
inject(:+)
end
end

Depois, basta incluir este mdulo em qualquer classe que ir ter este comportamento.
class Array
Array; include Summable
Summable; end
class Range
Range; include Summable
Summable; end

Toda vez que voc inclui um mdulo, ele adicionado como uma superclasse.
Array
Array.ancestors
#=> [Array, Summable, Enumerable, Object, Kernel, BasicObject]
Range
Range.ancestors
#=> [Range, Summable, Enumerable, Object, Kernel, BasicObject]

Voc pode verificar a lista de mdulos que foram includos com o mtodo Module.included_modules.
Array
Array.included_modules
#=> [Summable, Enumerable, Kernel]

O mtodo Module#include ir adicionar o mtodo como uma superclasse de self. No entanto, s vezes pode ser til
adicionar mtodos de instncia de um objeto em particular; neste caso, voc dever utilizar o mtodo Object#extend.

12

module Permalink
def to_permalink
self.to_s.downcase.gsub(/[^a-z0-9-]/, "-")
end
end
title = "Ruby Metaprogramming: I really like it"
title.extend Permalink
title.to_permalink
#=> "ruby-metaprogramming--i-really-like-it"

O mtodo Object#extend est disponvel em todos os objetos. Por isso, voc pode fazer algo como
module Person
extend self
def say
"Hi there!"
end
end

O mdulo Person est estendendo ele mesmo! Isso ir adicionar os mtodos de instncia do mdulo como mtodos do
prprio objeto (o mdulo) Person, tornando estes mtodos de classe.
Person
Person.say
#=> "Hi there!"

13

Captulo 1

Mtodos
Quando um mtodo invocado, Ruby ir procur-lo no prprio objeto. Caso ele no seja encontrado, este mtodo ser
procurado na classe Singleton. Se ele tambm no existir l, ento a busca ser feita na hierarquia da classe, passando
por cada uma das classes-pai deste objeto. Em ltimo caso, a exceo NoMethodError ser lanada. Este processo
chamado de method lookup.

14

O Ruby permite executar mtodos de diversas maneiras. A mais comum, obviamente, fazendo a chamada
diretamente no receiver.
class Person
def say
say(message)
puts message
end
end
person = Person
Person.new
person.say "Hello world!"

O nico problema com esta abordagem que voc precisa saber previamente qual o nome do mtodo. Felizmente, o
Ruby permite executar mtodos dinamicamente, como voc ver seguir.

Executando mtodos dinamicamente


Voc tambm pode utilizar o mtodo Object#__send__, que funciona exatamente como Object#send, mas que o
mtodo reservado, j que o mtodo Object#send pode ser sobrescrito. sempre uma boa ideia utilizar o mtodo
reservado, para garantir que seu cdigo funcione em qualquer situao.
Para executar mtodos dinamicamente, devemos utilizar o mtodo Object#send; este mtodo ir enviar uma
mensagem para o receiver, com os argumentos que o mtodo ir receber. O exemplo anterior pode ser representado
da seguinte forma:
person = Person
Person.new
person.send :say, "Hello world!"

O mtodo Object#send ir receber o nome do mtodo que vai ser executado (Person#say) e uma lista de argumentos.
Mtodos que esperam mais de um argumento podem ser executados como receiver.send(:method_name, arg1, arg2,
arg3).

15

Com o mtodo Object#send, o nome do mtodo torna-se apenas um argumento, permitindo que voc componha
dinamicamente o nome do mtodo que vai ser executado. Esta tcnica conhecida como Dynamic Dispatching e
muito utilizada.
class Person
def say
say(message)
puts message
end
def say_hello
say("Hey! Wassup?")
end
def say_goodbye
say("Gotta go! Seeya!")
end
end
person = Person
Person.new
%w[hello goodbye].each do |name|
person.send("say_#{
#{name}
}")
end

Outra vantagem1 do mtodo Object#send que voc pode executar mtodos privados sem lanar nenhum tipo de
exceo. Se voc no quiser executar mtodos privados, pode utilizar o mtodo Object#public_send, adicionado no
Ruby 1.9.
Executar mtodos dinamicamente pode ser uma mo na roda, mas voc no est limitado apenas execuo. O Ruby
tambm permite definir novos mtodos dinamicamente, como voc ver seguir.

Isto uma vantagem ou no, dependendo do ponto de vista. Se um mtodo privado, significa que voc no deveria
execut-lo, para comeo de conversa, a menos que tenha um motivo realmente forte para fazer isso.
16

Definindo novos mtodos


No Ruby, possvel definir novos mtodos em uma classe, aproveitando-se do conceito de classes abertas. Voc pode
definir um novo mtodo na classe Array, por exemplo.
Embora este tipo de monkey patching seja extremamente til, nem sempre uma boa ideia alterar mtodos
existentes, modificando seu comportamento.
class Array
def sum
inject(:+)
end
end

Para verificar que o mtodo funciona, podemos escrever o seguinte teste:


require "test/unit"
class TestArray < Test
Test::Unit
Unit::TestCase
TestCase
def test_sum
assert_equal 6, [1,2,3].sum
end
end

Mas nem sempre possvel utilizar esta tcnica. s vezes, precisamos definir dinamicamente novos mtodos; isso
pode ser feito com o mtodo Module#define_method, que espera o nome do novo mtodo e um bloco que ser o corpo
deste mtodo.
class Person
def say
say(message)
puts message
end
define_method :scream! do |message|

17

say(message.upcase)
end
end
person = Person
Person.new
person.scream! "Shut up!"
#=> SHUT UP!

O mtodo Module#define_method ir criar um mtodo de instncia na classe Person. Voc tambm pode criar mtodos
de classe, se executar o mtodo Module#define_method na classe Singleton deste objeto.
class Person
class << self
define_method :description do
puts "Hold a person information."
end
end
end
Person
Person.description
#=> "Holds person's information."

Voc tambm pode criar mtodos executando cdigo no contexto de objetos com os mtodos Kernel#eval,
2

Module#class_eval e BasicObject#instance_eval .
class Person
class_eval do
def say
say(message)
puts message
end
end
end

Voc ver mais sobre estes mtodos no captulo sobre Execuo de cdigo.
18

person = Person
Person.new
person.say "Hi!"

Redefinindo mtodos existentes


Embora definir novos mtodos seja uma funcionalidade extremamente til, s vezes queremos sobrescrever um
mtodo existente, estendendo seu comportamento. Uma das alternativas criar um alias deste mtodo, executando ou
no o mtodo original em nossa nova implementao.
class String
alias :original_downcase :downcase
def downcase
self.original_downcase.gsub(/(.)/m, '[\\1]')
end
end

Embora esta tcnica funcione, ela ir expor o mtodo String#original_downcase, mesmo no fazendo parte da
implementao. Alm disso, algum desenvolvedor pode substituir o mtodo String#original_downcase, o que pode
causar resultados inesperados.
Uma alternativa capturar o mtodo String#downcase e vincular este mtodo ao objeto, executando-o explicitamente
com o mtodo Method#call.
class String
downcase = instance_method(:downcase)
define_method :downcase do
downcase.bind(self).call.gsub(/(.)/m, '[\\1]')
end
end

Tambm possvel redefinir mtodos existentes de uma superclasse.

19

class Person
def initialize
initialize(options = {})
options.each {|name, value| instance_variable_set "@#{
#{name}
}", value }
end
end

O mtodo Person#initialize ir definir variveis de instncia baseado na combinao chave-valor do hash. Podemos
definir uma nova classe chamada Teacher, que ir estender o comportamento do mtodo Person#initialize.
class Teacher < Person
def initialize
initialize(options = {})
options.merge!(:role => "Teacher")
super
end
end

A palavra-chave super ir executar o mtodo de mesmo nome na superclasse desde objeto. Quando chamado sem
argumentos e sem parenteses, a palavra-chave super ir utilizar os mesmos argumentos do mtodo que a chamou; na
prtica, age como super(*args). Se voc quiser chamar super sem nenhum argumento, utilize super().

Removendo mtodos
Eventualmente, voc vai querer remover algum mtodo.
class Person
def say_hello
puts "Hi there!"
end
end
Person
Person.new.respond_to?(:say_hello)
#=> true
Person
Person.class_eval { remove_method :say_hello }

20

Person
Person.new.respond_to?(:say_hello)
#=> false

Note que o mtodo Module#remove_method privado; por isso preciso executar tal mtodo como vimos nos captulos
Mtodos: Execuo de cdigo.
O mtodo Module#remove_method ir remover o mtodo apenas do objeto em que foi chamado. Caso exista algum
mtodo de mesmo nome em alguma superclasse, o mtodo ainda ser executado.
class Parent
def message
"Hello from parent"
end
end
class Child < Parent
def message
"Hello from child"
end
end
Child
Child.new.message
#=> "Hello from child"
Child
Child.class_eval { remove_method :message }
Child
Child.new.message
#=> "Hello from parent"

Para evitar que isso acontea, voc pode utilizar o mtodo Module#undef_method. Este mtodo previne qualquer
chamada a um mtodo, mesmo que ele esteja disponvel atravs de alguma superclasse.
Child
Child.new.message
#=> "Hello from child"

21

Child
Child.class_eval { undef_method :message }
Child
Child.new.message
#=> NoMethodError: undefined method message for #<Child:0x0000010083ea90>

22

Captulo 1

Execuo de cdigo
O Ruby permite que voc execute strings e blocos de cdigo em runtime. Para isso, ele disponibiliza diversos mtodos
como Module#class_eval, Object#instance_eval e Kernel#eval, que atuam em contextos diferentes.

Module#class_eval
Os mtodos Module#class_eval e Module#instance_eval alteram o contexto de execuo para self. Desse modo,
possvel executar mtodos privados sem a necessidade de ter que usar o mtodo Object#send.
O mtodo Module#class_eval, que na verdade apenas um alias para Module#module_eval, permite executar cdigo no
contexto de uma classe ou mdulo. Ele muito utilizado para adicionar novos mtodos ou incluir mdulos em uma
classe.
class Person
end
Person
Person.class_eval do
def clap
puts "*clap* *clap* *clap*"
end
end
person = Person
Person.new
person.clap
#=> *clap* *clap* *clap*

Este mesmo exemplo poderia ter sido definido sem o uso de Module#class_eval, mas isso deixaria o cdigo muito
menos legvel.

23

Person
Person.send :define_method, :clap do
puts "*clap* *clap* *clap*"
end

Voc pode estar se perguntando porque o mtodo foi adicionado como sendo de instncia. A resposta bastante
simples: o mtodo Module#class_eval ir executar o cdigo utilizando a classe Person como seu contexto (self). Dessa
maneira, se voc quisesse adicionar um mtodo de classe, poderia ter adicionado algo como
Person
Person.class_eval do
def self.description
description
puts "Hold a person information."
end
end
Person
Person.description
#=> Hold a person information.

s vezes voc perceber que mais fcil executar cdigo como strings, em vez de passar um bloco, principalmente
quando precisar acessar muitas variveis de instncia.
Person
Person.class_eval do
%w[name age height].each do |name|
define_method(name) do |*args|
instance_variable_set("@#{
#{name}
}", args.first) unless args.empty?
instance_variable_get("@#{
#{name}
}")
end
end
end

O cdigo acima ir definir os mtodos Person#name, Person#age e Person#height, que se receberem um nico
argumento, atuam como setters. Caso contrrio, retornam o valor definido para aquele mtodo. Para definir e retornar
tais variveis, estamos utilizando os mtodos Object#instance_variable_set e Object#instance_variable_get. Se voc
precisa definir/ler muitas variveis, a legibilidade pode ficar comprometida e voc pode se perder em seu prprio
cdigo! Uma sada reescrever o cdigo, como no exemplo seguir.

24

class Person
%w[name age height].each do |name|
class_eval <<-RUBY, __FILE__, __LINE__
def #{
#{name}
}(*args)
@#{
#{name}
} = args.first unless args.empty?
@#{
#{name}
}
end
RUBY
end
end

# def age(*args)
#
@age = args.first unless args.empty?
#
@age
# @end

Esta abordagem muito utilizada no Ruby on Rails. Normalmente, tal cdigo vem acompanhado de comentrios que
mostram como cada linha ser executada.
Note que estamos passandos os argumentos __FILE__ e __LINE__. Isso permitir que o mtodo
Method#source_location retorne o lugar onde o mtodo foi definido. Esta informao tambm utilizada pelas
mensagens de erro.

Object#instance_eval
O mtodo Object#instance_eval similar ao mtodo Module#class_eval, com a diferena de que tambm pode ser
executado em instncias de objetos. Ao contrrio do mtodo Module#class_eval, est presente em todos1 os objetos.
person = Person
Person.new
Person
Person.respond_to?(:class_eval)
Person
Person.respond_to?(:instance_eval)

#=> true
#=> true

person.respond_to?(:class_eval)
person.respond_to?(:instance_eval)

#=> false
#=> true

Voc j deve estar cansado de saber, mas no Ruby classes tambm so objetos.
25

Alm disso, Object#instance_eval ir alterar o contexto de execuo para o prprio objeto (self), permitindo criar
DSLs2 muito facilmente.
Assim como o mtodo Module#class_eval, voc tambm pode definir novos mtodos.
person = Person
Person.new
person.instance_eval do
def say
say(message)
puts message
end
end
person.respond_to?(:say)
#=> true
person.say "hi"
#=> hi

Como voc est no escopo do objeto, possvel fazer tudo o que voc faria normalmente no escopo de um objeto,
inclusive executar mtodos privados.
person.instance_eval { @name = "John Doe" }
person.instance_variable_get("@name")
#=> John Doe

No captulo Criando DSLs voc ver que o mtodo Object#instance_eval tem um papel fundamental na criao de
DSLs.

Voc ver como utilizar todos os conceitos de metaprogramao juntos no captulo Construindo DSLs.
26

Kernel#eval
Ao contrrio dos outros mtodos, Kernel#eval permite executar apenas strings no contexto atual. Em contra-partida,
voc pode, opcionalmente, passar um contexto no qual esse cdigo ser executado.
eval "puts 1 + 1"
#=> 2

Embora o mtodo Kernel#eval no parea til, existem casos onde executar cdigo Ruby em tempo de execuo pode
ser uma boa ideia. o caso de Rake.
O Rake permite que voc defina suas tasks em um arquivo de build, normalmente chamado de Rakefile. Esse arquivo
lido do filesystem e executado em um contexto especfico, com acesso aos mtodos da biblioteca como desc e task.
Para entender melhor como este conceito funciona, vamos ler um arquivo que define as informaes pessoais de um
usurio. Tais informaes serviro para preencher os atributos de um objeto, que pode executar tarefas especficas
utilizando estes dados. Primeiro, crie um arquivo chamado ~/.aboutme com o seguinte contedo:
name "John Doe"
email "john@doe.com"
about "I'm just playing with Ruby code evaluation"

Nossa classe Person dever implementar os mtodos Person#name, Person#email e Person#about. Quando um
argumento passado, ir atuar como um setter. O cdigo ser o mesmo que usamos no exemplo mostrado em
Object#instance_eval, mas com um pouco de acar sinttico.
class Object
def self.dsl_attr
dsl_attr(name)
class_eval <<-RUBY, __FILE__, __LINE__
def #{
#{name}
}(*args)
@#{
#{name}
} = args.first unless args.empty?
@#{
#{name}
}
end
RUBY

# def about(*args)
# @about = args.first unless args.empty?
# @about
# @end

27

end
end

Agora, em nossa classe Person, podemos definir nossos atributos.


class Person
dsl_attr :name
dsl_attr :email
dsl_attr :about
end

Precisamos criar um mtodo que ir ler o arquivo ~/.aboutme e que ir executar o contedo do arquivo no contexto de
uma instncia da classe Person. Vamos chamar este mtodo de Person.from_manifest.
class Person
dsl_attr :name
dsl_attr :email
dsl_attr :about
def self.from_manifest
from_manifest
source = open(File
File.expand_path("~/.aboutme")).read
person = new
eval source, person.instance_eval { binding }
person
end
end

O cdigo acima muito simples. Primeiro, lemos o arquivo ~/.aboutme. Depois, instanciamos um novo objeto da classe
3

Person; note que utilizamos apenas new, pois o receiver padro self, que neste caso a prpria classe . Por ltimo,

executamos o cdigo lido no contexto da instncia da classe Person, retornado pelo mtodo Kernel#binding4.

Para saber mais sobre self, leia o captulo Entendendo o self.


28

person = Person
Person.from_manifest
person.name
#=> John Doe
person.email
#=> john@doe.com
person.about
#=> I'm just playing with Ruby code evaluation

O mtodo Kernel#eval no muito rpido; se voc puder, evite fazer muitas chamadas a este mtodo.

Kernel#binding um mtodo privado. Por este motivo, utilizamos o mtodo Object#send para execut-lo, como
vimos no captulo Mtodos.
29

Captulo 1

Callable Objects
O Ruby possui diversos objetos que so executveis, como o caso de mtodos e procs. Estes objetos respondem ao
mtodo call e iro executar algum cdigo associado ao objeto.

Procs e lambdas
Procs so objetos muito semelhantes a mtodos: voc pode execut-los e passar argumentos, com a diferena de que
eles no precisam estar necessariamente associados a um receiver.
Procs podem ser executados de diversas maneiras diferentes.
output = Proc
Proc.new {|message| puts message}
output.call("hi!")
#=> "hi!"
output["hi!"]
#=> "hi!"
output.("hi!")
#=> "hi!"

#=> Ruby 1.9+

A palavra-chave lambda permite criar uma funo annima, que nada mais que uma instncia da classe Proc. Voc
precisa fornecer um bloco, que ser usado como o corpo de sua funo.
say = lambda { puts "hi!" }
say.call
#=> "hi!"

30

Assim como mtodos, lambdas podem receber argumentos.


sum = lambda {|n1, n2| n1 + n2}
sum[1, 2]
#=> 3

No Ruby 1.9 voc pode at definir valores-padro para os argumentos!


say = lambda {|message = "you've got nothing to say"| puts message}
say["hi!"]
#=> "hi!"
say.call
#=> "you've got nothing to say"

Embora lambdas e procs sejam muito semelhantes, eles possuem duas diferenas muito importantes. A primeira que
lambdas podem forar o retorno com a palavra-chave return1. No caso de procs, o uso do return ir forar a sada do
mtodo em que o objeto estiver contido.
def returning
lambda { return }.call
puts "#1"
Proc
Proc.new { return }.call
puts "#2"
end
returning

No exemplo anterior, apenas a mensagem #1 ser exibida, j que Proc.new { return } ir parar a execuo do
mtodo.

Este comportamento foi introduzido no Ruby 1.9.


31

A outra diferena que procs criados com lambda iro verificar a quantidade de argumentos que foram passados; se
um lambda receber mais ou menos argumentos do que espera, a exceo ArgumentError ser lanada.
output = proc {|message| p message}
output.call
#=> nil
output = lambda {|message| p message}
output.call
#=> ArgumentError: wrong number of arguments (0 for 1)

Blocos
No Ruby, blocos no so objetos. Eles apenas fazem parte da sintaxe de chamada de um mtodo, por exemplo.
10.times {|i| puts i }

No exemplo acima, o receiver 10 est executando o mtodo times, que recebe um bloco. Infelizmente, no possvel
acessar diretamente o cdigo {|i| puts i}. O que voc precisa fazer capturar tal bloco como um objeto da classe
Proc.
O Ruby permite capturar blocos que so passados a um mtodo utilizando um argumento com um & em sua lista de
argumentos.
def say
say(&block)
puts block.call
end
say { "hi!" }
#=> "hi!"

32

Toda vez que voc utiliza esta sintaxe, o Ruby ir criar implicitamente uma instncia da classe Proc, atribuindo o bloco
ao argumento block. Este argumento pode ter qualquer nome; o que ir definir se ele ir armazenar o bloco passado
ao mtodo ou no a presena do &.
Voc no obrigado a passar um bloco para um mtodo, mesmo que ele tenha a definio de captura; neste caso,
voc precisar verificar se um bloco foi fornecido antes de utiliz-lo.
def say
say(&block)
if block_given?
puts block.call
else
puts "you've got nothing to say!"
end
end
say { "hi!" }
#=> "hi!"

Se voc quiser, pode omitir a captura do bloco e, ainda assim, execut-lo. para isto que serve a palavra-chave yield.
Do mesmo modo, voc precisar verificar se um bloco foi fornecido antes de utiliz-lo.
def say
if block_given?
puts yield
else
puts "you've got nothing to say!"
end
end
say { "hi!" }
#=> "hi!"

Mesmo sem definir a captura do bloco, possvel transformar o bloco fornecido em uma instncia da classe Proc.
Basta voc criar uma instncia sem passar nenhum bloco.

33

def say
if block_given?
block = Proc
Proc.new
puts block.call
else
puts "you've got nothing to say!"
end
end
say { "hi!" }
#=> "hi!"

Toda vez que voc cria uma instncia da classe Proc sem passar um bloco, o bloco que foi passado ao mtodo onde a
chamada est sendo executada que ser utilizado.
Voc tambm pode transformar procs em blocos. Basta utilizar o prprio & no ltimo argumento que identificar o
bloco.
say = lambda { puts "hi!" }
def output
output(&block)
yield
end
output(&say)
#=> "hi!"

Mtodos
O Ruby capaz de transformar mtodos em objetos. Este objeto uma instncia da classe Method e tambm um
objeto executvel, muito semelhante aos lambdas e procs.

34

class Person
def hi
puts "hi!"
end
end
person = Person
Person.new
hi = person.method(:hi)
hi.class
#=> Method
hi.call
#=> "hi!"

Se o mtodo envia algum argumento com a palavra-chave yield, voc pode passar um bloco.
def sum
sum(n1, n2)
yield n1 + n2
end
sum_method = method(:sum)
sum_method.call(1, 2) do |sum|
puts sum
#=> 3
end

Voc tambm pode transformar mtodos de instncia em objetos. No entanto, estes mtodos no estaro vinculados a
nenhum objeto, mas s podero ser executados depois que voc associ-los a uma instncia da classe que foi
definido.
class Person
def hi
puts "hi!"
end
end

35

hi = Person
Person.instance_method(:hi)
hi.class
#=> UnboundMethod
hi.call
#=> NoMethodError: undefined method call for #<UnboundMethod: Person#hi>
person = Person
Person.new
hi.bind(person).call
#=> "hi!"

O classe UnboundMethod no uma subclasse da classe Method. Somente aps invocar o mtodo UnboundMethod#bind,
que uma instncia da classe Method retornada. Neste momento, voc poder executar este mtodo como um objeto
executvel, como vimos anteriormente.
Se voc tentar associar este mtodo a uma classe diferente, receber a exceo TypeError, informando qual a classe
que voc deve utilizar.
class Car
Car; end
# hi.bind(Car.new).call
#=> TypeError: bind argument must be an instance of Person

36

Captulo 1

Constantes
No Ruby, toda e qualquer referncia que comea com uma letra maiscula, incluindo nome de classes e mdulos,
uma constante.
class Calculator
PI = 3.141592653589793
end

Neste exemplo, Calculator e Calculator::PI so constantes. Perceba que constantes respeitam o seu namespace;
possvel ter o mesmo nome de constante em diferentes namespaces.
module Company
SOME_CONSTANT = 1
class Person
SOME_CONSTANT = 2
end
end

Embora constantes sejam constantes na maioria das linguagens, o Ruby permite que sobrescreva uma constante
existente.
module Company
SOME_CONSTANT = 0
end
Company
Company::SOME_CONSTANT
SOME_CONSTANT = 1

37

Ao fazer isso, voc receber uma mensagem de alerta como esta: warning: already initialized constant
Company::SOME_CONSTANT. No entanto, possvel desativar tal mensagem se voc precisar sobrescrever uma constante;
basta alterar a flag $VERBOSE.
old_verbose, $VERBOSE = $VERBOSE, nil
Company
Company::SOME_CONSTANT
SOME_CONSTANT = 2
$VERBOSE = old_verbose

O ActiveSupport implementa esta funcionalidade elegantemente atravs do mtodo Kernel#silence_warnings.


silence_warnings { Company
Company::SOME_CONSTANT
SOME_CONSTANT = 3 }

Para retornar as constantes definidas em um namespace, voc pode utilizar o mtodo Module#constants.
Company
Company.constants
#=> [:SOME_CONSTANT]

Todas as constantes de primeiro nvel podem ser acessadas atravs do mtodo Object.constants.
Object
Object.constants
#=> [:Object, :Module, :Class, :Kernel, .., :Company]

Voc tambm pode verificar se uma constante foi definida com o mtodo Module#const_defined?.
Company
Company.const_defined?("SOME_CONSTANT")
#=> true
Company
Company.const_defined?("MISSING")
#=> false

Para pegar a referncia de uma constante, voc deve utilizar o mtodo Module#const_get.

38

Company
Company.const_get("SOME_CONSTANT")
#=> Company::SOME_CONSTANT

Se voc tentar referenciar uma constante que no existe, ir lanar a exceo NameError.
Company
Company.const_get("MISSING")
#=> NameError: uninitialized constant Company::MISSING

Para definir constantes dinamicamente, voc deve utilizar o mtodo Module#const_set.


Company
Company.const_set("ANOTHER_CONSTANT", 2)
Company
Company.const_defined?("ANOTHER_CONSTANT")
#=> true

Por ltimo, tambm possvel remover constantes; este o papel do mtodo Module#remove_const.
Company
Company.class_eval { remove_const("SOME_CONSTANT") }
#=> 1

Note que o mtodo Module#remove_const privado; por isso preciso executar tal mtodo como vimos nos captulos
Mtodos e Execuo de cdigo.
Voc pode remover qualquer constante, incluindo classes e mdulos.
class Person
end
Object
Object.class_eval { remove_const("Person") }
Object
Object.const_defined?("Person")
#=> false

39

Captulo 1

Hooks
O Ruby possui diversos hooks (ou callbacks) que permitem interceptar algumas aes do interpretador quando algum
evento ocorre. Estes hooks so apenas mtodos com nomes especficos que, quando presentes no contexto correto,
so executados pelo Ruby. Existem diversos hooks para eventos diferentes, como voc ver seguir.

Mdulos e Classes
included
executado quando um mdulo includo em outro1. Ele receber o mdulo que o incluiu como argumento.
Por padro, mtodos, constantes e variveis deste mdulo sero includas, a menos que este mdulo j seja uma
superclasse do objeto.
module SomeModule
def self.included
included(base)
puts "including #{
#{name}
} on #{
#{base.name}
}"
end
end
class SomeClass
include SomeModule
end

semelhante ao hook Module.append_features.


1

Isso tambm vlido para classes, j que Module uma superclasse de Class.
40

const_missing
executado quando uma constante no encontrada. Ele receber o nome da constante como argumento.
class Object
def self.const_missing
const_missing(const)
puts "The #{
#{const}
} constant has not been found"
end
end
User
#=> "The User constant has not been found"

extended
executado quando um objeto estendido com um mdulo. Ele receber o objeto como argumento.
module SomeModule
def self.extended
extended(object)
puts "Extending #{
#{object.inspect}
}"
end
end
some_object = Object
Object.new
some_object.extend(SomeModule
SomeModule)
#=> Extending #<Object:0x00000100847d70>

Este hook semelhante ao hook Module.extend_object.

inherited
executado quando um classe herdada por outra. Ele receber a subclasse como argumento.
class Parent
@descendants = []

41

def self.inherited
inherited(child)
@descendants << child.name unless @descendants.include?(child.name)
end
def self.descendants
descendants
@descendants
end
end
class Child < Parent
end
Parent
Parent.descendants
#=> ["Child"]

initialize_clone
executado toda vez que um objeto invoca o mtodo Object#clone. Ele receber o objeto como argumento. Por
padro, este hook ser delegado para Object#initialize_copy.

initialize_copy
executado toda vez que os mtodos Object#clone ou Object#dup forem invocados. Ele receber o objeto como
argumento.
class SomeClass
def initialize_clone
initialize_clone(object)
puts "You can't clone #{
#{object.inspect}
}"
end
end
SomeClass
SomeClass.new.clone
#=> "You can't clone #<SomeClass:0x0000010083e428>"

42

initialize_dup
executado toda vez que um objeto invoca o mtodo Object#dup. Ele receber o objeto como argumento. Por padro,
este hook ser delegado para Object#initialize_copy.

Mtodos
method_added
executado toda vez que um mtodo for adicionado a um objeto. Ele receber o nome do mtodo como argumento.
class SomeClass
def self.method_added
method_added(method)
puts "A new method has been added: #{
#{method}
}"
end
def do_something
end
#=> "A new method has been added: do_something"
end

method_missing
O Object#method_missing , sem dvida alguma, o hook mais utilizado do Ruby. Ele permite interceptar chamadas a
mtodos que no existem.
class Finder
def method_missing
method_missing(method, *args)
puts "Method #{
#{method}
} not found"
end
end
finder = Finder
Finder.new

43

finder.find_by_name
#=> "Method find_by_name not found"

Isso permite fazer coisas muito interessantes como construir chamadas dinmicas para mtodos, agir como um
delegator, criar um proxy para constantes enfim, as possibilidades so infinitas. Veja por exemplo, a biblioteca
Builder. Ela permite criar XMLs de uma maneira muito elegante, sem que voc precise chamar mtodos especficos da
biblioteca.
require "builder"
xml = Builder
Builder::XmlMarkup
XmlMarkup.new(:target => STDOUT, :indent => 2)
xml.instruct!
xml.person do |person|
person.name "John Doe"
person.phones do |phones|
phones.phone "1234-5678", :type => "work"
phones.phone "5678-1234", :type => "cellphone"
end
end

Este cdigo ir gerar um XML vlido como este:


<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>John Doe</name>
<phones>
<phone type="work">1234-5678</phone>
<phone type="cellphone">5678-1234</phone>
</phones>
</person>

O Object#method_missing timo quando voc quer agir como proxy para um mapeamento em hash, por exemplo.
class Person
MAPPING = {

44

:name
:age
:address
:phone

=>
=>
=>
=>

"John Doe",
32,
"455 Larkspur Dr., California Springs, CA 92926",
"1234-5678"

}
def method_missing
method_missing(method, *args)
MAPPING
MAPPING[method]
end
end
john = Person
Person.new
john.name
#=> "John Doe"
john.age
#=> 32

Mas o que acontece se acessarmos uma propriedade que no existe?


john.email
#=> nil

Aparentemente, ns substitumos a funcionalidade original do Object#method_missing. Este problema simples de


resolver; basta verificarmos se a chave existe e em caso negativo, executar o mtodo original.
class Person
MAPPING = {
:name
=>
:age
=>
:address =>
:phone
=>
}

"John Doe",
32,
"455 Larkspur Dr., California Springs, CA 92926",
"1234-5678"

def method_missing
method_missing(method, *args)

45

return MAPPING
MAPPING[method] if MAPPING
MAPPING.key?(method)
super
end
end

Agora, ao executar o mtodo Person#email, teremos algo como


john.email
#=> NoMethodError: undefined method email for #<Person:0x0000010101e0d0>

Sempre que voc sobrescrever o mtodo Object#method_missing, lembre-se tambm de sobrescrever o mtodo
Object#respond_to?. Isso porque em vez de verificar se o objeto uma instncia da classe (com os mtodos
Object#is_a? e Object#kind_of?), voc deve preferencialmente verificar se o objeto responde ao mtodo que voc
precisa 2.
class Person
MAPPING = {
:name
=>
:age
=>
:address =>
:phone
=>
}

"John Doe",
32,
"455 Larkspur Dr., California Springs, CA 92926",
"1234-5678"

def method_missing
method_missing(method, *args)
return MAPPING
MAPPING[method] if MAPPING
MAPPING.key?(method)
super
end
def respond_to?
respond_to?(method, include_private = false)
return true if MAPPING
MAPPING.key?(method.to_sym)
super
end
2

Este , basicamente, o conceito de Duck Typing: Se um objeto se parece com um pato, anda como um pato e fala
como um pato, ento deve ser um pato.
46

end
john = Person
Person.new
john.respond_to?(:name)
#=> true
john.respond_to?(:email)
#=> false

method_removed
executado toda vez que um mtodo for removido com o mtodo Module#remove_method. Ele receber o nome do
mtodo como argumento.
class SomeClass
def self.method_removed
method_removed(method)
puts "The method #{
#{method}
} has been removed"
end
def do_something
do_something; end
end
SomeClass
SomeClass.class_eval { remove_method :do_something }
#=> The method do_something has been removed

method_undefined
executado toda vez que um mtodo for removido com o mtodo Module#undef_method. Ele receber o nome do
mtodo como argumento.
class SomeClass
def self.method_undefined
method_undefined(method)
puts "The method #{
#{method}
} has been undefined"
end
def do_something
do_something; end

47

end
SomeClass
SomeClass.class_eval { undef_method :do_something }
#=> The method do_something has been undefined

singleton_method_added
executado toda vez que um mtodo singleton for adicionado a um objeto. Ele receber o nome do mtodo como
argumento.
class SomeClass
def self.singleton_method_added
singleton_method_added(method)
puts "The singleton method #{
#{method}
} has been added to #{
#{self.inspect}
}"
end
#=> The singleton method singleton_method_added has been added
def singleton_method_added
singleton_method_added(method)
puts "The singleton method #{
#{method}
} has been added to the instance"
end
end
object = SomeClass
SomeClass.new
def object.do_something
do_something; end
#=> "The singleton method do_something has been added to the instance"

singleton_method_removed
executado toda vez que um mtodo singleton for removido com o mtodo Module#remove_method. Ele receber o
nome do mtodo como argumento.
class SomeClass
def self.singleton_method_removed
singleton_method_removed(method)
puts "The singleton method #{
#{method}
} has been removed"
end
def self.do_something
do_something; end

48

end
SomeClass
SomeClass.singleton_class.class_eval { remove_method :do_something }
#=> The singleton method do_something has been removed

singleton_method_undefined
executado toda vez que um mtodo singleton for removido com o mtodo Module#undef_method. Ele receber o nome
do mtodo como argumento.
class SomeClass
def self.singleton_method_undefined
singleton_method_undefined(method)
puts "The singleton method #{
#{method}
} has been undefined"
end
def self.do_something
do_something; end
end
SomeClass
SomeClass.singleton_class.class_eval { undef_method :do_something }
#=> The singleton method do_something has been undefined

49

Captulo 1

Aprenda com exemplos


Incorporar a metaprogramao em seu dia-a-dia no uma tarefa das mais fceis. Voc provavelmente utilizar
diversas tcnicas vistas at agora; muitas vezes, voc precisar combinar mais do que uma destas tcnicas e a que a
coisa pode complicar.
Para ajudar voc, este captulo mostrar diversos patterns utilizados por diversos desenvolvedores, com uma
explicao sobre tal cdigo.

Registrando classes
Muitas bibliotecas podem ser estendidas atravs de novos adapters. Mas como saber quais so os adapters
disponveis? Uma das sadas registrar novas classes.
Neste exemplo, ns iremos escrever uma biblioteca chamada Formatter. Seu objetivo ser converter textos escritos em
diversas formataes como Textile e Markdown para HTML.
O primeiro passo escrever a classe que ser a base para todos os formatadores. Ela muitos simples: ter um
mtodo Formatter::Base#initialize que ir atribuir o contedo a ser convertido em uma varivel de instncia (assim
poderemos acess-la posteriormente) e um outro mtodo Formatter::Base#to_html que dever ser implementado por
cada um dos formatadores.
module Formatter
class AbstractMethodError < StandardError
StandardError; end
class Base
attr_accessor :content
def initialize
initialize(content)

50

@content = content
end
def to_html
raise Formatter
Formatter::AbstractMethodError
AbstractMethodError
end
end
end

O prximo passo criar um mtodo que ir converter o texto em HTML. Este mtodo receber um parmetro
indicando qual o tipo de markup do texto (Textile ou Markdown) e qual o contedo a ser convertido. Sua assinatura
ser algo como Formatter.format(type, content).
Precisaremos utilizar o argumento type para descobrir qual a classe que vamos usar. Uma alternativa utilizar um
hash que ir mapear este argumento com a classe responsvel.
module Formatter
class UnknownFormatterError < StandardError
StandardError; end
FORMATTERS = {
:textile => "Formatter::Textile",
:markdown => "Formatter::Markdown"
}
def self.format
format(type, content)
formatter_name = FORMATTERS
FORMATTERS[type.to_sym]
raise UnknownFormatterError unless formatter_name
formatter = eval(formatter_name)
formatter.new(content).to_html
end
end

Note que no estamos utilizando a classe em si; isso feito para evitar que o Ruby mantenha uma referncia de uma
classe em memria, mesmo sem utiliz-la. Em vez disso ns estamos utilizando apenas o nome da constante; para
pegar a referncia da classe iremos utilizar o mtodo Kernel#eval.

51

Agora, vamos escrever os formatadores.


require "RedCloth"
require "rdiscount"
module Formatter
class Textile < Base
def to_html
RedCloth
RedCloth.new(content).to_html
end
end
class Markdown < Base
def to_html
RDiscount
RDiscount.new(content).to_html
end
end
end

Todos os formatadores precisam apenas implementar o mtodo Formatter::Base#to_html. Agora podemos converter
textos para HTML utilizando uma interface nica.
Formatter
Formatter.format(:textile, "h1. Ruby Metaprogramming")
#=> "<h1>Ruby Metaprogramming</h1>"
Formatter
Formatter.format(:markdown, "# Ruby Metaprogramming")
#=> "<h1>Ruby Metaprogramming</h1>\n"

Mas o que acontece se quisermos adicionar um novo formatador com o RDoc? Precisaremos registrar este formatador
manualmente, adicionando uma nova entrada no hash.
require "rdoc/markup"
require "rdoc/markup/to_html"
module Formatter
class Rdoc < Base

52

def to_html
RDoc
RDoc::Markup
Markup::ToHtml
ToHtml.new.convert(content)
end
end
end
Formatter
Formatter::FORMATTERS
FORMATTERS[:rdoc] = "Rdoc"
Formatter
Formatter.format(:rdoc, "= Ruby Metaprogramming")
#=> "<h1>Ruby Metaprogramming</h1>\n"

A princpio, isso pode no parecer um problema, mas a verdade que quanto mais coisas tivermos que configurar,
maiores so as chances de algo sair errado. Podemos utilizar o mtodo Module.inherited para registrar
automaticamente os formatadores.
Primeiro, remova cada um dos tens do hash Formatter::FORMATTERS; eles sero adicionados automaticamente.
module Formatter
FORMATTERS = {}
end

Depois, vamos adicionar o mtodo Formatter::Base.inherited.


module Formatter
class Base
def self.inherited
inherited(child)
type = child.name.split("::").last.downcase.to_sym
Formatter
Formatter::FORMATTERS
FORMATTERS[type] = child.name
end
end
end

Com esta alterao no precisaremos mais registar as classes manualmente, evitando um ponto de falha.

53

method_missing e definio de mtodos


Nossa biblioteca Formatter possui um mtodo para converter textos para HTML. No entanto, seria interessante se toda
vez que adicionassemos um novo formatador, um mtodo com seu nome tambm fosse adicionado. Assim,
poderamos converter textos como em Formatter.textile("h1. Ruby Metaprogramming").
Existem dois caminhos possveis para esta implementao. O primeiro utilizando o mtodo Object#method_missing.
Toda vez que um mtodo no for encontrado, no verificamos se existe uma chave na constante
Formatters::FORMATTERS com o mesmo nome.
module Formatter
def self.method_missing
method_missing(method, *args)
return format(method.to_sym, args.first) if FORMATTERS
FORMATTERS.key?(method)
super
end
def self.respond_to?
respond_to?(method, include_private = false)
return true if FORMATTERS
FORMATTERS.key?(method)
super
end
end
Formatter
Formatter.textile("h1. Ruby Metaprogramming")
#=> "<h1>Ruby Metaprogramming</h1>"
Formatter
Formatter.respond_to?(:textile)
#=> true

Note que estamos implementando os mtodos Formatter.method_missing e Formatter.respond_to? como vimos em


Hooks: method_missing.
O outro caminho para implementarmos tal funcionalidade criar os mtodos toda vez que um novo formatador herdar
de Formatter::Base. Altere o mtodo Formatter::Base.inherited de modo que ele crie um mtodo com o mesmo
nome do formatador. Para isso, utilize o mtodo Module#class_eval.

54

module Formatter
class Base
def self.inherited
inherited(child)
type = child.name.split("::").last.downcase.to_sym
Formatter
Formatter::FORMATTERS
FORMATTERS[type] = child.name
Formatter
Formatter.class_eval <<-RUBY
def self.#{
#{type}
}(content)
format :#{
#{type}
}, content
end
RUBY
end
end
end

# def self.textile(content)
#
format :textile, content
# end

Normalmente, definir o mtodo dinamicamente mais rpido que utilizar o mtodo Object#method_missing. Na
dvida, faa um benchmark1 para saber qual o seu caso.

Definindo macros
No Ruby, macros so mtodos de classe que alteram/estendem objetos com novos comportamentos. Voc j deve
conhecer, por exemplo, os mtodos Module#attr_accessor, Module#attr_reader e Module#attr_writer. Quem utiliza o
Ruby on Rails, com certeza j viu a macro ActiveRecord::Base.has_many.
Vamos criar uma macro que permitir normalizar atributos antes de atribuir tal valor sua varivel. Como queremos
que nossa macro esteja disponvel para todas as classes, vamos adicion-la diretamente classe Object.
class Object
def self.attr_normalize
attr_normalize(name, options = {})
attr_reader name

Embora muitas pessoas digam que benchmarks so discutveis, eles podem nos ajudar a identificar pontos de
lentido no cdigo.
55

class_eval <<-RUBY
def #{
#{name}
}=(value)
@#{
#{name}
} = send(:#{
#{options[:with]}
}, value)
end
RUBY
end
end

# def name=(value)
#
@name = send(:normalize_whitespaces, value)
# end

class MyClass
attr_normalize :name, :with => :normalize_whitespaces
private
def normalize_whitespaces
normalize_whitespaces(value)
value.to_s.gsub(/\s+/, " ").gsub(/^ ?(.*?) ?$/, '\1')
end
end
object = MyClass
MyClass.new
object.respond_to?(:name)
#=> true
object.respond_to?(:name=)
#=> true
object.name = "\n\nsome
object.name
#=> "some awkward spaces"

awkward

spaces\n\n\t"

O Ruby executa a macro Object.attr_normalize na classe MyClass assim que ela definida. Como ela no passa de um
mtodo necessrio que ela exista previamente, como mostrado em Mtodos. Por isso ns definimos tal macro na
classe Object2, que uma superclasse de quase todos os objetos.

A exceo a classe BasicObject, que a superclasse de Object.


56

No caso do ActiveRecord, que usa macros ostensivamente, voc precisa criar uma macro na classe ActiveRecord::Base,
evitando poluir todo o namespace com funcionalidades especficas do ActiveRecord. Por exemplo, vamos criar uma
macro que definir um atributo de permalink.
Vamos criar um mdulo Permalink que estender a classe ActiveRecord::Base. Este mdulo ter um mtodo
Permalink.extended que ir disponibilizar a macro Permalink.permalink para todas as classes do ActiveRecord. Alm
disso, ele ir adicionar algumas validaes do atributo ActiveRecord::Base#permalink e um callback que ir gerar o
valor normalizado do permalink.
module Permalink
def permalink
permalink(attribute)
include Permalink
Permalink::InstanceMethods
InstanceMethods
class << self
attr_accessor :permalink_attribute
end
self.permalink_attribute = attribute
validates_presence_of :permalink
before_validation :generate_permalink
end
end

Agora precisamos escrever o mdulo Permalink::InstanceMethods. Este mdulo responsvel por definir todos os
mtodos que sero utilizados pela instncia do ActiveRecord na hora de gerar o permalink.
module Permalink
module InstanceMethods
def to_param
"#{
#{id}
}-#{
#{permalink}
}"
end
private
def generate_permalink
write_attribute :permalink, value_for_permalink

57

end
def value_for_permalink
__send__(self.class.permalink_attribute).to_s.downcase.gsub(/[^a-z0-9]/, "-")
end
end
end

O mtodo Permalink::InstanceMethods#generate_permalink apenas define o valor do atributo. Quem faz a


normalizao o mtodo Permalink::InstanceMethods#value_for_permalink. Perceba que estamos utilizando o
mtodo Object#__send__ para pegar o valor do atributo que ser normalizado.
J o mtodo Permalink::InstanceMethods#to_param utilizado na gerao de rotas. Ele chamado quando voc passa
a instncia para um dos helpers de url, como em article_path(article).
Agora s falta estendermos a classe ActiveRecord::Base.
ActiveRecord
ActiveRecord::Base
Base.extend(Permalink
Permalink)

Ao estender a classe ActiveRecord::Base, ns tornaremos o mtodo Permalink.permalink disponvel em todas as


classes de ActiveRecord. Para utilizar esta macro, basta cham-lo em nossa classe.
class Post < ActiveRecord
ActiveRecord::Base
Base
permalink :title
end
post = Post
Post.create(:title => "Ruby Metaprogramming is a nice technique")
post.permalink
#=> "ruby-metaprogramming-is-a-nice-technique"

58

Criando DSLs
DSLs (Domain-Specific Languages) tem uma relao muito prxima com a metaprogramao no Ruby. Para criar uma
DSL voc ir utilizar diversas tcnicas vistas at agora e, muitas vezes, ao mesmo tempo.
Criar DSLs exige muito mais que saber quais so as tcnicas corretas. DSLs exigem um conhecimento mais
aprofundado sobre o domnio do problema. E isso nem sempre fcil. Alm disso, manter uma DSL pode ser
trabalhoso. Antes de partir para este caminho, pese estes fatores com cuidado.

yield
Uma maneira de criar uma DSL recebendo um bloco que receber algum argumento. Este o caminho escolhido pelo
RSpec para sua configurao.
RSpec
RSpec.configure do |config|
config.color_enabled = true
end

Agora, ns vamos criar nosso prprio mdulo de configurao. Primeiro defina os atributos configurveis.
module MyLib
module Config
class << self
attr_accessor :name
attr_accessor :description
end
end
end

Do modo como nosso mdulo foi definido, precisaremos especificar o receiver toda hora que formos definir uma nova
configurao.

59

MyLib
MyLib::Config
Config.name = "My Awesome Lib"
MyLib
MyLib::Config
Config.description = "My Awesome Lib does amazing stuff"

Para facilitar esta configurao, ns podemos criar um mtodo que ir passar o mdulo MyLib::Config para o bloco.
module MyLib
def self.configure
configure(&block)
yield Config
end
end
MyLib
MyLib.configure do |config|
config.name = "My Awesome Lib"
config.description = "My Awesome Lib does amazing stuff"
end

Embora esta tcnica tenha diminudo o rudo do cdigo, ela nem sempre til, principalmente quando todas as
chamadas que voc fizer forem em um receiver especfico. Neste caso, uma alternativa melhor utilizar o mtodo
Object#instance_eval.

instance_eval
Como vimos em Execuo de Cdigo: Object#instance_eval, o mtodo Object#instance_eval permite alterar o
contexto de execuo de um bloco. Ele , sem dvida, um dos mais importantes mtodos na criao de DSLs internas
no Ruby.
Vamos criar uma classe que permitir escrever receitas culinrias. Embora esse exemplo seja muito utilizado, ele
perfeito pois aplica um domnio muito fcil de ser entendido.
Primeiro vamos criar nossa classe Recipe. Ela possui alguns atributos necessrios para definir uma nova receita.
class Recipe
attr_accessor :name
attr_accessor :description
attr_accessor :ingredients

60

attr_accessor :instructions
def initialize
@ingredients = []
@instructions = []
end
end

Normalmente, voc iria criar uma instncia e definir cada uma das propriedades.
recipe = Recipe
Recipe.new
recipe.name = "Caipirinha"
recipe.ingredients << "12 limes"
recipe.ingredients << "1 litro de cachaa"
recipe.ingredients << "acar gosto"
recipe.ingredients << "gelo gosto"

Embora no seja uma tarefa das mais complicadas, seria melhor se pudessemos escrever isso de uma forma mais
elegante. Vamos, ento, alterar nosso mtodo Recipe#initialize de modo que ele execute um bloco, quando
disponvel.
class Recipe
def initialize
initialize(&block)
@ingredients = []
@instructions = []
instance_eval(&block) if block_given?
self
end
end

Agora, podemos definir os atributos de uma outra maneira.


recipe = Recipe
Recipe.new do
self.name = "Capirinha"

61

self.ingredients
self.ingredients
self.ingredients
self.ingredients
end

<<
<<
<<
<<

"12 limes"
"1 litro de cachaa"
"acar gosto"
"gelo gosto"

Voc no precisa definir o receiver quando for utilizar um getter, mas ele necessrio se estiver definindo um valor
atravs de um setter. Neste caso, nossa DSL no ficou muito diferente da verso inicial. Para evitar que isso seja
necessrio, ns iremos utilizar o mtodo Object#dsl_attr que criamos em Execuo de Cdigo: Kernel#eval. Altere a
definio do atributos.
class Recipe
dsl_attr :name
dsl_attr :description
dsl_attr :ingredients
dsl_attr :instructions
end

Agora podemos utilizar uma sintaxe um pouco mais agradvel.


recipe = Recipe
Recipe.new do
name "Capirinha"
description "A caipirinha uma das bebidas brasileiras mais conhecidas internacionalmente."
ingredients
ingredients
ingredients
ingredients

<<
<<
<<
<<

"12 limes"
"1 litro de cachaa"
"acar gosto"
"gelo gosto"

instructions << "Aps lavar o limo, descasca-se parcialmente o mesmo."


instructions << %Q[Corta-se o limo em duas metades, removendo de cada
uma delas a parte central (branca) do bagao.]
instructions << %Q[Aps os procedimentos usuais usam-se dois copos iguais,
bem colados entre si para efetuar a mistura, como se fosse uma coqueteleira.]
end

62

Para finalizar, vamos implementar um mtodo que ir formatar as informaes da receita de um modo mais legvel.
class Recipe
def to_text
"".tap do |text|
text << name
text << "\n" << ("=" * name.size) << "\n"
text << "\n" << description if description
text << "\n"
text << "\nIngredientes" << "\n------------\n\n"
text << ingredients.collect {|i| "- #{
#{i}
}\n"}.join("")
text << "\nComo preparar" << "\n-------------\n\n"
text << instructions.each_with_index.collect {|i, n| "#{
#{n + 1}
}. #{
#{i}
}\n"}.join("")
end
end
end

Se voc executar o mtodo Recipe#to_text ter uma sada como


Capirinha
=========
A caipirinha uma das bebidas brasileiras mais conhecidas internacionalmente.
Ingredientes
------------

12 limes
1 litro de cachaa
acar gosto
gelo gosto

Como preparar
-------------

63

1. Aps lavar o limo, descasca-se parcialmente o mesmo.


2. Corta-se o limo em duas metades, removendo de cada
uma delas a parte central (branca) do bagao.
3. Aps os procedimentos usuais usam-se dois copos iguais,
bem colados entre si para efetuar a mistura, como se fosse uma coqueteleira.

Interfaces Fluentes
Interfaces fluentes permitem executar diversos mtodos em cadeia, normalmente formando uma sentena que tornar
a leitura do cdigo mais agradvel. Um exemplo de interface fluente o ARel, que simplifica a gerao de SQL no
ActiveRecord.
User
User.where(:email => "john@doe.com").includes(:profile).limit(1).first

Para criar sua prpria interface fluente, preciso que voc sempre retorne o receiver aps definir o atributo.
class SQL < BasicObject
def select
select(fields)
@select = fields
self
end
def from
from(tables)
@from = tables
self
end
def where
where(conditions)
@where = conditions
self
end
def to_sql
"".tap do |query|
query << "SELECT #{
#{@select}
} FROM #{
#{@from}
}"

64

query << " WHERE #{


#{build_conditions}
}" if @where
end
end
private
def build_conditions
@where.collect {|name, value| %[#{
#{name}
} = "#{
#{value}
}"]}.join(" AND ")
end
end

Obviamente nossa implementao falha, j que no implementa todos os comandos SQL, nem sanitiza os dados
informados pelo usurio, mas voc pegou a ideia.
sql = SQL
SQL.new
sql.select("*").from("users").where(:email => "john@doe.com")
sql.to_sql
#=> SELECT * FROM users WHERE email = "john@doe.com"
sql = SQL
SQL.new
sql.select("*").from("users")
sql.to_sql
#=> SELECT * FROM users

Perceba que a implentao dos mtodos basicamente a mesma: atribua um valor varivel de instncia e passe o
prprio objeto como retorno do mtodo. Ns podemos evitar duplicaes gerando estes mtodos dinamicamente.
class SQL
%w[select from where includes limit joins offset].each do |name|
class_eval <<-RUBY
def #{
#{name}
}(value)
@#{
#{name}
} = value
self
end
RUBY
end
end

65

E para finalizar
A melhor maneira de se usar a metaprogramao saber quando no us-la. Ao longo deste e-book voc deve ter
percebido que esta uma tcnica extremamente poderosa. No entanto, o uso desnecessrio da metaprogramao
pode tornar seu cdigo mais difcil de entender, ou o que pior, manter. Principalmente se quem tiver que manter o
cdigo no souber esta tcnica.
como todos ns j sabemos: com grandes poderes, vem grandes responsabilidades.

66

Copyright Simples Ideias & Nando Vieira. Todos os direitos reservados.


Veja outras publicaes em http://howtocode.com.br.

Potrebbero piacerti anche