#1 Metaprogramação: Ruby Object Model

09 February 2010

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
comments powered by Disqus