A arte de escrever programas que escrevem programas está presente desde o início da ciência da computação, com o objetivo de aumentar a produtividade diminuindo a quantidade de código a ser escrito. O prefixo grego meta está presente na nossa língua, tornando mais fácil o entendimento do conceito por nós brasileiros logo de cara, bastando apenas lembrar das aulas de literatura ou dos livros de Machado de Assis.

Machadão, o rei da Metaliteratura

Machadão, o rei da Metaliteratura

Metaclasses

Lembra daquela frase que dizia que “em Ruby tu é objeto”? Pois é, vamos olhar o seguinte código:

class Zombie
  def self.alive
    puts "i am alive"
  end
end

Zombie.class     # => Class
Zombie.alive      # => "i am alive"
z = Zombie.new
z.class              # => Zombie

Quando definimos uma classe (no caso a Zombie), uma constante (que em Ruby são escritas com a primeira letra em maiúsculo, lembram?) é criada com o mesmo nome para armazenar um objeto do tipo Class. Assim, quando chamamos um método dessa “constante”, essa mensagem é passada para um objeto Class que o representa.

É interessante frisar que objetos não armazenam métodos, apenas classes fazem isso. Então quando invocamos um método de Zombie, na verdade, este método está armazenado em um objeto do tipo Class.

Vamos brincar mais um pouco:

Zombie.to_s      #=> "Zombie"

Ué? De onde esse método saiu se não o definimos?

O que ocorre aqui é que aquele objeto mencionado anteriormente, que representaria uma classe Zombie, recebe no momento de sua criação todos os métodos com o sufixo “self” ( que representam os métodos de classe) e herda todos os métodos da classe Class. Nesse exemplo, to_s é um método da classe Class. Na verdade, a definição de Zombie é uma classe virtual criada dinamicamente para armazenar os métodos da classe. Esses objetos de classe são chamados de Metaclasses.

Resumindo: Uma classe também é um objeto, mas de um tipo bem particular! Mais precisamente, um objeto da classe Class.

Metaprogramação em Ação

Vamoz continuar brincando com a nossa classe Zombie. É possível que criemos variáveis em tempo de execução dentro de apenas uma instância:

z = Zombie.new
z.instance_variable_set("@eyes", 3)
z.eyes                         # =>NoMethodError: undefined method `eyes' for #

Opa, será que não funcionou o nosso exemplo? Na verdade, sim. Só que não possuímos nenhum método de acesso à propriedade @eyes. Podemos garantir isso, através do seguinte método:

z.instance_variables        # =>  ["@eyes"]

Mas como faríamos para acessar a propriedade @eyes de nosso zumbi? Isso nos leva ao próximo tópico:

Classes Singleton

Primeiramente, esqueça completamente o Design Pattern Singleton . As classes Singleton de Ruby só compartilham o nome com este padrão. Singleton class pode ser resumida como uma classe virtual, aonde um objeto de uma classe já definida pode assumir um comportamento particular, diferente de outros objetos da mesma classe. Vejamos abaixo:

def z.eyes
  "I have #{@eyes} eyes"
end

z.eyes   #  => "I have 3 eyes"

Nesse exemplo nós estamos criando um método apenas para a instância(objeto) “z” da classe Zombie. Agora nosso objeto “z” é um classe Singleton, única, com um comportamento próprio. Podemos perguntar para um objeto se ele possui métodos singleton:

z.singleton_methods             #=> ["eyes"]

Certo, vamos pirar mais um pouco na batatinha. Observe este exemplo:

y = Zombie.new

class << y
  def brains_eaten
    "millions"
  end
end

y.singleton_methods        # => ["brains_eaten"]
y.brains_eaten                 #=> "millions"

Essa sintaxe até certo ponto estranha para iniciantes é o jeito Ruby de dizer que você está “abrindo” uma classe singleton do objeto y . Mas as coisas interessantes não param por aí. Podemos fazer Singletons de instâncias de classes como no exemplo a seguir:

class Fixnum
  class << self
    def desc
     puts "another description"
    end
  end
end

Fixnum.desc        # => another description

Ou, para ficar mais familiar, podemos repetir o mesmo exemplo desta maneira, que é bem comum quando manipulamos objetos do ActiveRecord:

class Fixnum
  def self.description
    puts "i am a number"
  end
end

Fixnum.description         # => i am a number

O que fizemos foi simplesmente adicionar um método dinamicamente usando linguagem de programação, ou seja Metapgrogramação.

Por último, ficaremos com um idioma clássico do Ruby, que está presente em qualquer brincadeira que se presente de metaprogramação:

class << self; self; end

Esta pequena e bizarra linha, nos retornará uma classe singleton da classe que a utilizar, como demonstrado no exemplo abaixo:

class Screamer

  self.module_eval do
    define_method :scream do
      puts "I am a instance method!! AAAAAAH"
    end
  end

  (class << self; self; end).module_eval do
    define_method :scream do
      puts "I am a class method! AAAAAAH"
    end
  end

end

Screamer.scream         # => I am a class method! AAAAAAH
Screamer.new.scream     #I am a instance method!! AAAAAAH

Por enquanto é só. No futuro retomarei esse assunto com exemplos melhores para tornar tudo mais claro na prática.

Referências e links para se aprofundar no assunto:

Ruby Metaprogramming techniques por Ola Bini

Seeing Metaclasses Clearly por Whytheluckystiff

Understanding Ruby Singleton Classes por Peter Jones.