Creando Dinámicamente los Métodos dentro de method_missing

Podemos usar define_method dentro de missing_method para crear el método fantasma. define_method define un método de instancia en el objeto receptor. La sintáxis es:

     define_method(symbol, method)     => new_method
     define_method(symbol) { block }   => proc

El parámetro method puede ser un Proc o un Method. Si se usa un bloque como parámetro, es usado como cuerpo del método. El bloque es evaluado utilizando instance_eval. Puesto que define_method es privado y de clase, no podemos llamarlo directamente dentro de method_missing. Al ser privado no podemos llamarlo explicitamente en la clase. Dentro de method_missing la variable self refiere al objeto, y define_method no es un método de instancia. Una solución es llamarlo via Kernel.send.

MacBook-Air-de-casiano:rubytesting casiano$ cat -n chamcham3.rb 
     1  class A
     2    def method_missing(n, *a)
     3      super unless %{aa bb cc dd}.include? n.to_s
     4  
     5      m = Kernel.send :define_method, n do 
     6        puts "Inside #{n}" 
     7      end
     8      m[]
     9      puts "Inside method_missing, building #{n}" 
    10    end
    11  end
    12  
    13  a = A.new()
    14  a.aa()
    15  a.aa()
    16  a.aa()
    17  a.ee()

MacBook-Air-de-casiano:rubytesting casiano$ ruby  chamcham3.rb 
Inside aa
Inside method_missing, building aa
Inside aa
Inside aa
chamcham3.rb:3:in `method_missing': undefined method `ee' for #<A:0x10513dcf0> (NoMethodError)
    from chamcham3.rb:17

Otra forma de hacerlo es usar class_eval:

MacBook-Air-de-casiano:rubytesting casiano$ cat -n chamcham5.rb 
     1  class A
     2  
     3    def method_missing(n, *a)
     4      super unless %{aa bb cc dd}.include? n.to_s
     5      A.class_eval {
     6        m = define_method(n) do 
     7          puts "Inside #{n} #{self.class.ancestors.inspect}" 
     8        end
     9        m[]
    10      }
    11      puts "Inside method_missing, building #{n}" 
    12    end
    13  end
    14  
    15  a = A.new()
    16  a.aa()
    17  a.aa()
    18  a.aa()

MacBook-Air-de-casiano:rubytesting casiano$ ruby chamcham5.rb 
Inside aa [Class, Module, Object, Kernel]
Inside method_missing, building aa
Inside aa [A, Object, Kernel]
Inside aa [A, Object, Kernel]

O bien llamar a un método de clase desde el cual se llame a defined_method:

MacBook-Air-de-casiano:rubytesting casiano$ cat -n chamcham4.rb 
     1  class A
     2    def self.build_method(n)
     3      m = define_method(n) do 
     4        puts "Inside #{n} #{self.class.ancestors.inspect}" 
     5      end
     6      m[]
     7    end
     8  
     9    def method_missing(n, *a)
    10      super unless %{aa bb cc dd}.include? n.to_s
    11      self.class.build_method(n)
    12      puts "Inside method_missing, building #{n}" 
    13    end
    14  end
    15  
    16  a = A.new()
    17  a.aa()
    18  a.aa()
    19  a.aa()

MacBook-Air-de-casiano:rubytesting casiano$ ruby chamcham4.rb 
Inside aa [Class, Module, Object, Kernel]
Inside method_missing, building aa
Inside aa [A, Object, Kernel]
Inside aa [A, Object, Kernel]

Casiano Rodriguez León 2015-06-18