Para quem como eu, vem de linguagens não-dinâmicas, o que mais impressiona (e dificulta no início) é a capacidade de Ruby em se moldar e executar códigos dinamicamente. E seguindo a nossa saga pelos conceitos de metaprogramação, vamos explorar um pouco mais um recurso interessantíssimo de Ruby chamado eval.

Ruby Eval

O nome eval tem origem na palavra inglesa evaluation, que em uma tradução livre significa “avaliar o valor de algo”, no caso de Ruby, o valor de uma expressão ou trecho de código.

No caso de Ruby, a melhor definição seria “interpretar”, devido ao fato que além de avaliar, o código recebido também é executado, como no exemplo abaixo:

eval("puts 'Hello World'") # => Hello World

No post anterior sobre metaprogramação, eu utilizei o método instance_variable_set da classe Object.

Esse método pode ser utilizado para evitar repetições de código. No código abaixo eu mostro como é a implementação desse método e como ele é usado na prática:

Implementação redundante de um construtor de uma classe:

class CalendarEvent
  def initialize(start_time=0, end_time=0, attendees=0)
    @start_time = start_time
    @end_time = end_time
    @attendees = attendees
  end
end

Implementação melhorada com o uso de eval:

#Primeiro, adicionamos o seguinte código à classe Object,
#para torná-lo disponível para as instâncias de quaisquer classe
class Object
  private
  def set_instance_variables(binding, *variables)
    variables.each do |var|
      instance_variable_set("@#{var}", eval(var, binding))
   end
  end
end

#Depois podemos melhorar a nossa classe

class CalendarEvent
  def initialize(start_time=0, end_time=0, attendees=0)
    set_instance_variables(binding, *local_variables)
  end
end

c = CalendarEvent.new(4,5,2)

puts c.instance_eval "@start_time"   # => 4

Em Ruby, temos mais alguns métodos do tipo eval disponível:

* instance_eval
* class_eval
* module_eval

Dentre esses, a diferença básica está no escopo aonde os métodos serão executados: em classes ou instâncias.

Module.class_eval

O método da classe Module, chamado class_eval, nos possibilita executar blocos dentro do escopo de uma classe. Uma possibilidade útil criada por esse método é a de criar dinamicamente métodos a uma classe, como no exemplo do attr_accessor, que é implementado em C, mas se fosse implementado em Ruby seria algo do tipo:

class Class
  def attr_acc(*vars)
    return if vars.empty?
    code = ""
    vars.each do |v|
      # Cria gets para as variaveis
      code << "def #{v}; @#{v}; end;"
      # Cria sets para as variaveis
      code << "def #{v}=(value); @#{v} = value; end;"
    end
    self.class_eval code

  end
end

#podemos criar uma classe que utiliza nossa cópia do attr_acc
class Foo
attr_acc :bar
end

#... e testar pra ver se ela funciona
f = Foo.new
f.bar = "hello"
puts f.bar                  # => hello

Module.module_eval

Na verdade, podemos realizar algo parecido com o exemplo anterior com uma sintaxe levemente diferente usando Mocule.module_eval como no exemplo abaixo, aonde eu crio um método que retorna os 5 primeiro múltiplos de 3:

Fixnum.module_eval do
  def self.multiple_of_three
     [3,6,9,12,15]
  end
end
puts  Fixnum.multiple_of_three
#=> 3
6
9
12
15

É importante ressaltar que no exemplo acima, estamos criando dinamicamente um método na classe Fixnum, e não um método para as instâncias dessa classe, de modo que a chamada abaixo acabará nos retornando uma exceção:

5.multiple_of_three   # =>NoMethodError: undefined method `multiple_of_three' for 5:Fixnum

Object.instance_eval

O método intance_eval, da classe Object, por sua vez implementa a função eval sob o escopo de instâncias.

class Foo
  def initialize
    @bar = 'hey'
  end
  def change_bar
    @bar = "ho!"
  end
end

foo = Foo.new
puts foo.instance_eval "@bar"
foo.change_bar
puts foo.instance_eval "@bar"

Conclusão

Metaprogramação acaba ficando mais claro na medida que começamos a compreender o verdadeiro poder oferecido pela linguagem. Uma ótima fonte de inspiração e estudo é o código de projetos largamente utilizados em Ruby.

É com recursos como esse que o Rails (e outras ferramentas escritas em Ruby) adicionam diversas funcionalidades ao core de Ruby para comtemplar suas “mágicas”.