#1 Metaprogramação: Ruby Object Model

Posted on February 9, 2010

10


O primeiro passo para compreender os conceitos de metaprogramação em Ruby é conhecer como a linguagem forma os objetos, ou seja, entender o fluxo de onde estão realmente os métodos e variáveis das suas classes e objetos. Esse modelo de organização é chamado Ruby Object Model.

De onde veio esse método ? O que realmente ocorre ao incluir um módulo ? São perguntas que podem ser respondidas analisando o modelo de objetos do Ruby.

Classes Abertas

Vamos analisar o seguinte problema. Precisamos de um método que, dada uma string contendo caracteres especiais, o retorno seja a mesma string sem tais caracteres. Para facilitar a compreensão, vamos criar um test unitário e a implementação do método:

Funciona, porém não de uma forma muito orientada a objetos. Não é novidade que em Ruby podemos adicionar métodos as classes em tempo de execução, inclusive nas classes da biblioteca padrão do Ruby. Portanto, poderíamos adicionar nosso método diretamente na classe String:

Mas…o que acontece com os outros métodos da classe String ?
Agora temos duas definições para a classe String ?
Vejamos como exemplo a seguir. A classe Teste é definida duas vezes.

Quando o código se refencia a classe Teste pela primeira vez, nenhuma classe existia. Então o Ruby define nesse momento a classe Teste e o método x.
Na segunda vez que a classe Teste é referenciada ela já existe, portanto o Ruby não precisa defini-la de novo. Ele apenas reabre a classe e define o método y.

É possível abrir uma classe Ruby e redefinir comporamento a qualquer momento, isso são as Classes Abertas(Open Class), mas obviamente é necessário pensar bem antes de abrir uma classe do próprio Ruby e mudar algum comportamento.

Obs: Muitas gems fazem uso dessa técnica, como o Money, que insere o método to_money na classe Numeric.

Monkey-Patch

Agora suponha que você tem um Array de Strings. Nesse array você deseja procurar todas as palavras “verde” e substituí-las por “azul”. Nessa caso você poderia definir um método chamado replace:

Funciona, porém olhe os métodos já existentes na classe Array:

Esse é o lado complicado de classes abertas, também conhecido como monkey-patch. Você pode mudar o comportamento de outros arrays que utilizavam o método replace. Existem prós e contras na utilização de monkey patch, ou seja, devem ser utilizados corretamente.

Objetos

Veja a classe abaixo:


Em Ruby não existe conexão entre a classe de um objeto e suas variáveis de instância. Isso significa que se o método my_method não for chamado o objeto não teria essa variável.

O importante aqui é: Variáveis de instância vivem nos objetos enquanto métodos vivem nas classes. O método my_method é um método de instância de MyClass(e não apenas um método), o que significa que ele foi definido em MyClass e para chamá-lo você precisa de uma instância de MyClass.

Veja outro exemplo:

Perceba que os métodos que podem ser chamados na instância “abc” são os mesmos obtido pelo método instance_methods diretamente na classe String. Porém os métodos que você pode chamar em uma instância como “abc” não são os mesmos que pode chamar na classe String.

Lookup e Execution

Para entender a chamada de métodos no Ruby precisamos compreender duas fases.
Object em Ruby é a superclass de todas as classes (ou BasicObject a partir do Ruby 1.9).

Quase todo objeto possui uma classe e superclasse. Na classe descobrimos quais métodos de instância podem ser chamados. Nas superclasses herdamos outros métodos. Vejamos as classes abaixo:

Com isso chegamos em um diagrama bem interessante sobre o Ruby Object Model:

Method Lookup

A primeira coisa para descobrir de onde vem um método no objeto Ruby é fazer analisar sua classe e subir por suas superclasses. Essa estratégia é conhecida como “one step to the right, then up”.

Para entender esse conceito de cadeias na chamada de métodos, percorra a classe e suas subclasses, pode-se usar o método ancestors para isso, até chegar em Object e BasicObject.

Módulos

Repare que aparece o módulo Kernel logo após Object. Por quê? Toda vez que uma classe inclui um módulo ela entra na corrente logo após a classe que a incluiu. Muitas gems tiram vantagens disso incluindo métodos no módulo Kernel.
(O RSpec, por exemplo, que inclui should e should_not em todos os objetos)

Metaprogramação – Continua …

Conhecer sobre o modelo de objetos ruby é obrigatório para entender metaprogramação. Esse post ainda poderia ir muito longe em questões como receiver, self, method execution, etc, assuntos que serão tratados em próximos posts.

Continua
Próximo post – Definindo métodos