by Richo Healey
What if it were possible to call methods with spaces in their name directly from Ruby?
If you’ve seen Gary Bernhardt’s awesome talk where he digs into some of the quirks of Ruby, you’ll know that it’s pretty trivial to get bare words in Ruby:
1 2 3 4 5 6
Disappointing to say the least. Obviously something is amiss. It turns out this is just a quirk in irb; if you try instead
1 2 3 4 5 6 7
Cool, so what else can we do with this? It’s trivial to define a method with a space in its name, and calling it isn’t terribly difficult:
1 2 3 4 5 6 7 8
But having created such a monstrosity, how do you call it from the repl? Or for that matter, from an actual Ruby program? This is obviously something you should be doing in production…
1 2 3 4 5 6 7 8 9 10 11 12 13
Bam. You may be looking at this baffled (or if you’re reasonably tight with metaprogramming in Ruby, sharpening/setting fire to something with a view to causing me significant bodily harm).
Walking through this, we first of all act on whatever
self is; in most cases this will be the local scope. If we didn’t do this, we’d be defining the method on
Object, which can cause all kinds of headaches when you’re trying to debug.
Immediately after this, we unpack arguments if they look like they were created by an earlier instance of this method. This is unwieldy, but unfortunately Ruby’s single return values and the recursion we’re employing here make it necessary. We could definitely define a subclass of
Array to make the test cleaner and the implementation more robust, but I preferred to keep this as short as possible and use the bare minimum number of Ruby primitives.
Once we’ve unpacked our arguments, we do the real magic. First off, we split our arguments into
NameErrors, the container we’re using for our missing method names, and everything else (the legitimate arguments we were called with).
We try to find a method with the current name (as we’ll be building our method name right to left with recursive calls to
method_missing), and failing that we pack up our current attempt with our arguments, and return it for the next pass.
There are enough issues with this (if you defined the methods
foo bar baz and
bar baz, a call to
foo bar baz would call
bar bazs return) to make it unwieldy. On the other hand; if those bugs are the only thing stopping you from putting this into production, you’ve probably got larger issues.
If this large scale abuse of the language excites you, you might be interested to know that we’re hiring.
At this point you’re probably eager to know.. does it work?
1 2 3 4 5 6 7 8 9