2010-07-10 24 views
12

Estoy definiendo dinámicamente un método en un módulo, y me gustaría comprobar que una vez que el método está vinculado a una instancia de clase que el cuerpo del método es lo que yo ' Estoy esperando. ¿Hay alguna manera de producir (como texto) el cuerpo de un método?Meta-programación: cuerpo de método de salida como texto

Módulo controller_mixins.rb:

module ControllerMixin 

    instance_eval "def search_by_vendor (*args) \n" \ 
    " @#{self.class.name.sub(/Controller/, '').tableize} = #{self.class.name.sub(/Controller/, '')}.find_all_by_vendor_id(params[:vendor_id]) \n"\ 
    "respond_to do |format| \n" \ 
    " format.html { render :template=>'/#{self.class.name.sub(/Controller/, '').tableize}/index', :layout=>'vendor_info'} \n" \ 
    " format.xml { render :xml => @#{self.class.name.sub(/Controller/, '').tableize} } \n" \ 
    "end \n"\ 
    "end \n" 

end 

clase se mezcla con:

class VendorOrdersController < ApplicationController 
    # GET /vendor_orders 
    # GET /vendor_orders.xml 
    require 'controller_mixins' 
    include ControllerMixin 
<rest of class> 

así que me gustaría ver la implementación del mixin cuando se aplica a VendorOrdersController probablemente a través de script/console por conveniencia.

ACTUALIZACIÓN: Per @ ~/Guardé la cadena a una variable y puts 'd it. Eso funcionó perfectamente. Lo que sacó a la luz un error en mi código (la razón por la que quería ver el código en primer lugar). El código a continuación es mucho mejor y funciona como se espera.

module ControllerMixin 

    def self.included(mod) 
    method_body = "def search_by_vendor \n" \ 
     " @#{mod.name.sub(/Controller/, '').tableize} = #{mod.name.sub(/Controller/, '')}.find_all_by_vendor_id(params[:vendor_id]) \n"\ 
     "respond_to do |format| \n" \ 
     " format.html { render :template=>'/#{mod.name.sub(/Controller/, '').tableize}/index', :layout=>'vendor_info'} \n" \ 
     " format.xml { render :xml => @#{mod.name.sub(/Controller/, '').tableize} } \n" \ 
     "end \n"\ 
    "end \n" 

    puts method_body 
    mod.class_eval(method_body) 
    end 

end 
+0

define_method es su amigo – shingara

+0

Probablemente querrá utilizar 'aquí documentos' ('CÓDIGO << - ... CODE') en lugar de cadenas concatenadas. El formato es mucho más agradable. – Chubas

+0

Por qué no he oído hablar de estos 'aquí documentos' Este es un enviado de DIOS. Gracias. Buena explicación (en el blog de otra persona): http://blog.commonthread.com/2007/12/20/tip-ruby-here-document – SooDesuNe

Respuesta

5

No, no puede obtener el código fuente detrás de un método.

Lo mejor que puede hacer es obtener el objeto Method que representa el método usando Object#method. Por ejemplo:

m = VendorOrdersController.method(:search_by_vendor) 

Pero usted encontrará que no hay mucho más que una Method#name, Method#arity, Method#source_location, etc.

En su caso, sin embargo, ¿por qué no simplemente almacenar la cadena en una variable, imprimir antes de usar instance_eval?

Independientemente, su instance_eval se ejecutará en el momento de la declaración del módulo. Es probable que desee envolverlo en una devolución de llamada included para que se ejecute en el momento de la inclusión.

module ControllerMixin 
    def self.included(mod) 
    mod.instance_eval([...]) 
    end 
end 
+0

Lo sentimos Shtééf, acabo de notar que el hecho mismo punto en la mitad de su respuesta . Dejaré el mío por el momento ya que alguien más podría extrañarlo como yo. – Joc

+1

¿No puedes ejecutar '.method (: search_by_vendor) .source'? – tekknolagi

2

La mejor manera de asegurarse de obtener su resultado deseado como resultado ... es escribir una prueba.

Además, no estoy de acuerdo con el uso de ejemplo_eval. Si DEBES metaprogramar así, use define_method, o probablemente podría escapar sin hacer nada al pasar un parámetro de las rutas, claro, es un poco más tipeo, pero esa metaprogramación es simplemente asquerosa.

+0

Parece que define_method básicamente promueve un bloqueo a un método. Dado que no sabré el nombre de mi método o las variables internas, ¿cómo puedo definir esto como un bloque en general? – SooDesuNe

+0

@SooDesuNe: Debido a que el bloque que da a 'define_method' es un cierre, se tiene acceso a las variables locales. Solo inténtalo; en su caso, es trivial y será ** mucho ** más agradable. –

2

¿No pudo asignar la cadena a una variable antes de que instance_eval se ejecute y lo envíe a la consola?

Como su cadena define el método completo, usted ya tiene el código fuente.

0

como las respuestas anteriores dicen, la mejor manera de hacer lo que estás tratando de hacer es con define_method.

si alguien está buscando "Meta-programación: cuerpo del método de salida como texto" intente esto:

si desea obtener el código de una clase o un método echa un vistazo a ParseTree y ruby2ruby

ParseTree puede tomar el Árbol de sintaxis abstracta de una clase o método y genera "expresiones simbólicas", como lisp, parece listas de listas de símbolos, y es muy interesante.

ruby2ruby toma estas s-exps y lo convierte en rubí regular.

ser advertidos, árbol de traducción no funciona actualmente en Ruby 1.9.

Cuestiones relacionadas