Meta-programming: output method body as text

2020-08-11 10:39发布

问题:

I'm dynamically defining a method in a module, and I'd like to check that once the method is bound to a class instance that the body of the method is what I'm expecting. Is there a way to output (as text) of the body of a method?

Module 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

class being mixed with:

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

So I'd like to see the implementation of the mixin when applied to VendorOrdersController probably via script/console for convenience.

UPDATE: Per @~/ I saved the string to a variable and puts'd it. That worked perfectly. Which brought to light an error in my code (the reason I wanted to see the code in the first place). Code below is much better, and works as expected.

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

回答1:

No, you cannot get the source code behind a method.

The best you can do is get the Method object that represents the method using Object#method. For example:

m = VendorOrdersController.method(:search_by_vendor)

But you'll find that there's not much more than a Method#name, Method#arity, Method#source_location, etc.

In your case however, why not simply store the string in a variable, print it, before using instance_eval?

Regardless, your instance_eval will be executed at the moment of module declaration. You probably want to wrap it in an included callback to have it executed at the moment of inclusion.

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


回答2:

The best way to ensure you get your intended output as a result ... is to write a test.

Also - i don't approve of using instance_eval like that. If you MUST metaprogram like that, use define_method, or you could probably get away without doing any of that by passing a parameter from the routes, sure, it's a little more typing, but that much metaprogramming is just icky.



回答3:

Couldn't you assign the string to a variable before instance_eval runs and output it to the console?

As your string defines the whole method you essentially already have the source code.



回答4:

as above answers say, the best way to do what you are trying to do is with define_method.

if anyone is looking for "Meta-programming: output method body as text" try this:

if you want to get the code of a class or a method check out ParseTree and ruby2ruby

ParseTree can grab the Abstract Syntax Tree of a class or method and generates "symbolic expressions", like lisp, it looks likes lists of lists of symbols, and is very interesting.

ruby2ruby takes these s-exps and turns it into regular ruby.

be warned, ParseTree does not currently work in ruby 1.9.