Metaprogramming with String Evaluations






Metaprogramming with String Evaluations

Problem

You're trying to write some metaprogramming code using define_method, but there's too much reflection going on for your code to be readable. It gets confusing and is almost as frustrating as having to write out the code in longhand.

Solution

You can define new methods by generating the definitions as strings and running them as Ruby code with one of the eval methods.

Here's a reprint of the metaprogramming example from the previous recipe, which uses define_method:

	class Numeric
	 [['add', '+'], ['subtract', '-'],
	  ['multiply', '*',], ['divide', '/']].each do |method, operator|
	    define_method("#{method}_2") do
	      method(operator).call(2)
	    end
	  end
	end

The important line of code, method(operator).call(2), isn't something you'd write in normal programming. You'd write something like self + 2 or self / 2, depending on which operator you wanted to apply. By writing your method definitions as strings, you can do metaprogramming that looks more like regular programming:

	class Numeric
	  [['add', '+'], ['subtract', '-'],
	   ['multiply', '*',], ['divide', '/']].each do |method, operator|
	    module_eval %{ def #{method}_2
	                     self.#{operator}(2)
	                   end }
	  end
	end

	4.add_2                                                  # => 6
	10.divide_2                                              # => 5

Discussion

You can do all of your metaprogramming with define_method, but the code doesn't look a lot like the code you'd write in normal programming. You can't set an instance variable with @foo=4; you have to call instance_variable_set('foo', 4).

The alternative is to generate a method definition as a string and execute the string as Ruby code. Most interpreted languages have a way of parsing and executing arbitrary strings as code, but it's usually regarded as a toy or a hazard, and not given much attention. Ruby breaks this taboo.

The most common evalutation method used for metaprogramming is Module#module_eval. This method executes a string as Ruby code, within the context of a class or module. Any methods or class variables you define within the string will be attached to the class or module, just as if you'd typed the string within the class or module definition. Thanks to the variable substitutions, the generated string looks exactly like the code you'd type in manually.

The following four pieces of code all define a new method String#last:

	class String
	  def last(n)
	    self[-n, n]
	  end
	end
	"Here's a string.".last(7)                   # => "string."

	class String
	  define_method('last') do |n|
	   self[-n, n]
	  end
	end
	"Here's a string.".last(7)                   # => "string."

	class String
	  module_eval %{def last(n)
	                  self[-n, n]
	                end}
	end
	"Here's a string.".last(7)                   # => "string."

	String.module_eval %{def last(n)
	                       self[-n, n]
	                     end}

	"Here's a string.".last(7)                   # => "string."

The instance_eval method is less popular than module_eval. It works just like module_eval, but it runs inside an instance of a class rather than the class itself. You can use it to define singleton methods on a particular object, or to set instance variables. Of course, you can also call define_method on a specific object.

The other evaluation method is just plain eval. This method executes a string exactly as though you had written it as Ruby code in the same spot:

	class String
	  eval %{def last(n)
	           self[-n, n]
	         end}
	end
	"Here's a string.".last(7)                   # => "string."

You must be very careful when you use the eval methods, lest the end-user of a program trick you into running arbitrary Ruby code. When you're metaprogramming, though, it's not usually a problem: the only strings that get evaluated are ones you constructed yourself from hardcoded data, and by the time your class is loaded and ready to use, the eval calls have already run. You should be safe unless your eval statement contains strings obtained from untrusted sources. This might happen if you're creating a custom class, or modifying a class in response to user input.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows