Updated: Scott Labounty wondered how the trace example could work and since a typical metaprogramming technique is writing before- and after-methods, I have added a small version of this.
Updated: Fixed two typos, found by Stephen VilesI have been thinking much about Metaprogramming lately. I have come to the conclusion that I would like to see more examples and explanations of these techniques. For good or bad, metaprogramming has entered the Ruby community as the standard way of accomplishing various tasks, and to compress code. Since I couldn't find any good resources of this kind, I will start the ball running by writing about some common Ruby techniques. These tips are probably most useful for programmers that come to Ruby from another language or haven't experienced the joy of Ruby Metaprogramming yet.
1. Use the singleton-classMany ways of manipulating single objects are based on manipulations on the singleton class and having this available will make metaprogramming easier. The classic way to get at the singleton class is to execute something like this:
sclass = (class << self; self; end)
RCR231 proposes the method Kernel#singleton_class with this definition:
module Kernel
def singleton_class
class << self; self; end
end
end
I will use this method in some of the next tips.
2. Write DSL's using class-methods that rewrite subclassesWhen you want to create a DSL for defining information about classes, the most common trouble is how to represent the information so that other parts of the framework can use them. Take this example where I define an ActiveRecord model object:
class Product < ActiveRecord::Base
set_table_name 'produce'
end
In this case, the interesting call is set_table_name. How does that work? Well, there is a small amount of magic involved. One way to do it would be like this:
module ActiveRecord
class Base
def self.set_table_name name
define_attr_method :table_name, name
end
def self.define_attr_method(name, value)
singleton_class.send :alias_method, "original_#{name}", name
singleton_class.class_eval do
define_method(name) do
value
end
end
end
end
end
What's interesting here is the define_attr_method. In this case we need to get at the singleton-class for the Product class, but we do not want to modify ActiveRecord::Base. By using singleton_class we can achieve this. We have to use send to alias the original method since alias_method is private. Then we just define a new accessor which returns the value. If ActiveRecord wants the table name for a specific class, it can just call the accessor on the class. This way of dynamically creating methods and accessors on the singleton-class is very common, and especially so in Rails.
3. Create classes and modules dynamicallyRuby allows you to create and modify classes and modules dynamically. You can do almost anything you would like on any class or module that isn't frozen. This is very useful in certain places. The Struct class is probably the best example, where
PersonVO = Struct.new(:name, :phone, :email)
p1 = PersonVO.new(:name => "Ola Bini")
will create a new class, assign this to the name PersonVO and then go ahead and create an instance of this class. Creating a new class from scratch and defining a new method on it is as simple as this:
c = Class.new
c.class_eval do
define_method :foo do
puts "Hello World"
end
end
c.new.foo # => "Hello World"
Apart from Struct, examples of creating classes on the fly can be found in SOAP4R and Camping. Camping is especially interesting, since it has methods that creates these classes, and you are supposed to inherit your controllers and views from these classes. Much of the interesting functionality in Camping is actually achieved in this way. From the unabridged version:
def R(*urls); Class.new(R) { meta_def(:urls) { urls } }; end
This makes it possible for you to create controllers like this:
class View < R '/view/(\d+)'
def get post_id
end
end
You can also create modules in this way, and include them in classes dynamically.
4. Use method_missing to do interesting thingsApart from blocks, method_missing is probably the most powerful feature of Ruby. It's also one that is easy to abuse. Much code can be extremely simplified by good use of method_missing. Some things can be done that aren't even possible without. A good example (also from Camping), is an extension to Hash:
class Hash
def method_missing(m,*a)
if m.to_s =~ /=$/
self[$`] = a[0]
elsif a.empty?
self[m]
else
raise NoMethodError, "#{m}"
end
end
end
This code makes it possible to use a hash like this:
x = {'abc' => 123}
x.abc # => 123
x.foo = :baz
x # => {'abc' => 123, 'foo' => :baz}
As you see, if someone calls a method that doesn't exist on hash, it will be searched for in the internal collection. If the method name ends with an =, a value will be set with the key of the method name excluding the equal sign.
Another nice method_missing technique can be found in Markaby. The code I'm referring to makes it possible to emit any XHTML tags possible, with CSS classes added into it. This code:
body do
h1.header 'Blog'
div.content do
'Hellu'
end
end
will emit this XML:
<body>
<h1 class="header">Blog</h1>
<div class="content">
Hellu
</div>
</body>
Most of this functionality, especially the CSS class names is created by having a method_missing that sets attributes on self, then returning self again.
5. Dispatch on method-patternsThis is an easy way to achieve extensibility in ways you can't anticipate. For example, I recently created a small framework for validation. The central Validator class will find all methods in self that begin with check_ and call this method, making it very easy to add new checks: just add a new method to the class, or to one instance.
methods.grep /^check_/ do |m|
self.send m
end
This is really easy, and incredibly powerful. Just look at Test::Unit which uses this method all over the place.
6. Replacing methodsSometimes a method implementation just doesn't do what you want. Or maybe it only does half of it. The standard Object Oriented Way (tm) is to subclass and override, and then call super. This only works if you have control over the object instantiation for the class in question. This is often not the case, and then subclassing is worthless. To achieve the same functionality, alias the old method and add a new method-definition that calls the old method. Make sure that the previous methods pre- and postconditions are preserved.
class String
alias_method :original_reverse, :reverse
def reverse
puts "reversing, please wait..."
original_reverse
end
end
Also, a twist on this technique is to temporarily alias a method, then returning it to before. For example, you could do something like this:
def trace(*mths)
add_tracing(*mths) # aliases the methods named, adding tracing
yield
remove_tracing(*mths) # removes the tracing aliases
end
This example shows a typical way one could code the add_tracing and remove_tracing methods. It depends on singleton_class being available, as per tip #1:
class Object
def add_tracing(*mths)
mths.each do |m|
singleton_class.send :alias_method, "traced_#{m}", m
singleton_class.send :define_method, m do |*args|
$stderr.puts "before #{m}(#{args.inspect})"
ret = self.send("traced_#{m}", *args)
$stderr.puts "after #{m} - #{ret.inspect}"
ret
end
end
end
def remove_tracing(*mths)
mths.each do |m|
singleton_class.send :alias_method, m, "traced_#{m}"
end
end
end
"abc".add_tracing :reverse
If these methods were added to Module (with a slightly different implementation; see if you can get it working!), you could also add and remove tracing on classes instead of instances.
7. Use NilClass to implement the Introduce Null Object refactoringIn Fowlers Refactorings, the refactoring called Introduce Null Object is for situations where an object could either contain an object, or null, and if it's null it will have a predefined value. A typical exampel would be this:
name = x.nil? ? "default name" : x.name
Now, the refactoring is based on Java, which is why it recommends to create a subclass of the object in question, that gets set when it should have been null. For example, a NullPerson object will inherit Person, and override name to always return the "default name" string. But, in Ruby we have open classes, which means you can do this:
def nil.name; "default name"; end
x # => nil
name = x.name # => "default name"
8. Learn the different versions of evalThere are several versions of evaluation primitives in Ruby, and it's important to know the difference between them, and when to use which. The available contestants are eval, instance_eval, module_eval and class_eval. First, class_eval is an alias for module_eval. Second, there's some differences between eval and the others. Most important, eval only takes a string to evaluate, while the other can evaluate a block instead. That means that eval should be your absolutely last way to do anything. It has it's uses but mostly you can get away with just evaluating blocks with instance_eval and module_eval.
Eval will evaluate the string in the current environment, or, if a binding is provided in that environment. (See tip #11).
Instance_eval will evaluate the string or the block in the context of the reveiver. Specifically, this means that self will be set to the receiver while evaluating.
Module_eval will evaluate the string or the block in the context of the module it is called on. This sees much use for defining new methods on modules or singleton classes. The main difference between instance_eval and module_eval lies in where the methods defined will be put. If you use String.instance_eval and do a def foo inside, this will be available as String.foo, but if you do the same thing with module_eval you'll get String.new.foo instead.
Module_eval is almost always what you want. Avoid eval like the plague. Follow these simple rules and you'll be OK.
9. Introspect on instance variablesA trick that Rails uses to make instance variables from the controller available in the view is to introspect on an objects instance variables. This is a grave violation of encapsulation, of course, but can be really handy sometimes. It's easy to do with instance_variables, instance_variable_get and instance_variable_set. To copy all instance_variables from one object to another, you could do it like this:
from.instance_variables.each do |v|
to.instance_variable_set v, from.instance_variable_get(v)
end
10. Create Procs from blocks and send them aroundMaterializing a Proc and saving this in variables and sending it around makes many API's very easy to use. This is one of the ways Markaby uses to manage those CSS class definitions. As the pick-axe details, it's easy to turn a block into a Proc:
def create_proc(&p); p; end
create_proc do
puts "hello"
end # => #<Proc ...>
Calling it is as easy:
p.call(*args)
If you want to use the proc for defining methods, you should use lambda to create it, so return and break will behave the way you expect:
p = lambda { puts "hoho"; return 1 }
define_method(:a, &p)
Remember that method_missing will provide a block if one is given:
def method_missing(name, *args, &block)
block.call(*args) if block_given?
end
thismethoddoesntexist("abc","cde") do |*args|
p args
end # => ["abc","cde"]
11. Use binding to control your evaluationsIf you do feel the need to really use eval, you should know that you can control what variables are available when doing this. Use the Kernel-method binding to get the Binding-object at the current point. An example:
def get_b; binding; end
foo = 13
eval("puts foo",get_b) # => NameError: undefined local variable or method `foo' for main:Object
This technique is used in ERb and Rails, among others, to set which instance variables are available. As an example:
class Holder
def get_b; binding; end
end
h = Holder.new
h.instance_variable_set "@foo", 25
eval("@foo",h.get_b)
Hopefully, some of these tips and techniques have clarified metaprogramming for you. I don't claim to be an expert on either Ruby or Metaprogramming. These are just my humble thoughts on the matter.