Ruby Meta-Programming Gotchas
One of the neat features of Ruby is its support for meta-programming. That is code that, essentially, writes code. For example, my Rails ApplicationController has a method called instance, which uses the model name to return an instance variable using instance_variable_get, like this:
def instance
instance_variable_get "@#{model_name}"
end
Another powerful technique is the use of method_missing (which I always want to call missing_method for some reason). Rails itself uses this to implicitly add getters and setters for model instance’s attributes.
Danger, Will Robinson!
However, like any powerful feature, there are some dangers that go along with using these techniques. A couple of the more obvious ones:
Bypassing private and protected. Ruby has an interesting approach when it comes to meta-programming: it assumes that if you know enough to use these kind of features, you don’t need the programming language to protect you from yourself. By definition, instance_variable_get gives you access to private data. Another method, send gives you access to private methods. This can have some interesting side-effects, as we’ll see below.
Unexpected invocation of method_missing. Defining method_missing means that erroneous method invocations will now simply invoke your method_missing. This can lead to strange bugs and infinite loops. For example, from within the class that defines method_missing, even a reference to an undefined local variable can invoke method_missing, since Ruby thinks it might be a method invocation.
But Dan, What Can I Do?
The number one rule about these methods is probably don’t use them unless you have to! However, that’s not much fun, since arguably you never really have to. I coded in C++ for years and did just fine for the most part, and C++ could not play these kinds of tricks – and least not easily. But one of the things that the Rails folks have reminded us about is that there is nothing wrong with wanting to enjoy what you do. And meta-programming is fun.
So, having decided that we will use missing_method or method_missing or whatever it is, what can we do to keep from hurting ourselves? Here are a couple of handy little rules:
- Don’t use meta-programming to access private or protected data. Generally, if you are doing this, you are breaking the interface to the class. If you want to add to the interface, re-open the class definition and add the necessary accessors or whatever. At least that way you aren’t violating the encapsulation of the class.
In the example above, instance is defined on the controller. The instance_variable_get method is being used entirely because it provides a simple way to access the “instance” of the controller without knowing what the instance variable name is, since it varies from controller to controller. Notice that this, in turn, makes it possible for other methods to access the instance variable for the controller without using instance_variable_get.
- Save
method_missingfor last. There is nothing wrong with implementing a method likeget_dynamic_attributeor something like that, and then later, once you have the kinks worked out, changing it tomethod_missing. That way, while you are debugging, you don’t accidentally invokemethod_missingand confuse things. Also, you might find that it actually makes more sense to have an explicit interface for such a method. And, after all that, if you still feel likemethod_missingimproves your interface, you can add it quite easily, like this:
def method_missing(method,*args)
get_dynamic_attribute(method,*args)
end
- Use
superinmethod_missing.method_missingallows you to implicitly define methods on a class. However, it also gets invoked even for methods you didn’t intend to define. In those cases, you need to pass alongmethod_missingto the superclass. For example, Rails does this in ActiveRecord objects if you invoke a method that is not actually an attribute of the class. That way, the programmer still gets a missing method exception if they attempt to access a method that you never wanted to define, instead of some mysterious error that might not even make sense to you.
Which means the above wrapper code should really look like this:
def method_missing(method,*args)
get_dynamic_attribute(method,*args) rescue super
end
- Don’t use
sendandmissing_methodtogether. More on this below.
Send Buddhism
Let’s examine one of the mysteries of send. Actually, it isn’t really a mystery of send so much as Object. As I mentioned above, send bypasses private / protected distinctions – you can invoke any method on an object using send. This is very consistent with other meta-programming methods in Ruby. What makes this interesting is that there are a lot of methods defined on Object as private methods. They get pulled in from Kernel and, because they are private, they are easy to overlook. In fact, it is this bit of magic that allows you to use Kernel methods, like puts without referencing an Object.
Here is a quick irb session that demonstrates what I’m talking about here:
irb(main):002:0> class Foo ; end
=> nil
irb(main):003:0> Foo.new.puts "hello"
NoMethodError: private method `puts' called for #
from (irb):3
irb(main):004:0> Foo.new.send :puts, "hello"
hello
=> nil
However, what this means is that when you use send, you have to remember that all these methods become part of the interface. There are a lot of these and so the interface may not be what you expect it to be when using send. In combination with other meta-programming techniques, including the use of missing_method, this can lead to very unexpected results.
method_not_missing
This can come up quite naturally. For example, suppose you want to parse a template that gives you access to an ActiveRecord attribute. You get the name of the attribute and naturally decide to use send to invoke the accessor. However, the accessor happens to be named “display”. Guess what? Instead of the ActiveRecord object’s missing_method (as he corrects himself for the 17th time in this blog entry after writing method_missing), you will get Object#display, which will quietly print the object on standard out and return nil.
To your template parsing code, it looks as though the value of the display attribute is nil, when in fact, you have no idea what the value might be. There is no error or exception, either, and you’d have to look closely at your logs to see that anything at all was happening out of the ordinary. This is the kind of bug you can have nightmares about years later.
This is another benefit to having an explicit interface for the implicit methods. Then, instead of using send, you can use that method. This is a flaw, in my opinion, of the current ActiveRecord implementation. An improved version would explicitly provide an accessor function in place of the current method_missing, and then simply have method_missing invoke that function, catching any exceptions and calling super.