Ruby XML Part 4 – Correction and Elucidation

Hola amigos. I know it’s been a long time since I rapped at you on my Ruby XML stuff, and some of these posts have been pretty dunce-tastic, so I know all my readers were looking forward with bated breath to the next installment, wherein I would no doubt prove that I had grown incrementally smarter. Wait no longer.

OK so first things first. This thing I’ve messed around with recently isn’t a service — it’s a client. We must make sure that we get the nomenclature correct so that we’ll know what we’re all talking about. The thing on the other end is the service. I was referring to it as a service in kind of a generic way, but it wasn’t too helpful because this is most definitely properly called a client.

So what kinds of changes have I made recently? Well first off, I decided that this snippet from my last post was just painful to the point of utter crappiness:

1
2
w = WebEx::Request.new
w.send_request(XMLObject.create_attendee(@attendee))

Not only is it ugly, it’s long-winded and hard to remember. As soon as I had another person working with this code at work, I felt the sting of shame and knew I had to re-factor. I decided that there was no reason not to take the opportunity to use one of Ruby’s vaunted metaprogramming features to try and make this a little shorter. Now it works like this:

1
2
w = WebEx::Request.new
w.create_attendee(@attendee)

Maybe #method_missing?

So how did I accomplish that? Simple, I used Ruby’s method_missing to enable arbitrary method calls to be made on the WebEx::Request object.

method_missing is a hook method that gets called when there’s no definition found for a method called on an object — i.e. the method is missing. When that happens, the Ruby interpreter automatically calls an instance method called method_missing. Usually, that’s not defined, so the interpreter will raise an exception, but if method_missing is defined, the interpreter does whatever it says to. Here, I’m defining it like this:

1
2
3
def method_missing(request_method, *args)
  self.send_request(WebEx::XMLObject.send(request_method.to_sym, *args))
end

In this case, self is of course an object of the WebEx::Request class. When the interpreter tries to call #create_attendee on an instance of that class, it can’t find the method and method_missing is called. My implementation of method_missing assumes that the “method” being called on the WebEx::Request object is a real class method of the XMLObject class (which is where all my actual API calls are), so it calls it there, passing along the arguments it received in the first place.

Of course, if no such class method exists, the interpreter throws an exception like it should. But for legitimate calls, I’ve shortened my code by quite a bit.

<gasp> isn’t it bad to use method_missing?

Some might argue that this is a cavalier usage of method_missing. I don’t really think so. The entirety of my request class is only like three methods other than this. It exists only to make a request and process the result, but it does that dumbly because it actually uses XMLObjects for the real meat of both. WebEx::Request is just a dumb object acting on stuff at the direction of the WebEx::XMLObjects that it calls through XMLObject class methods. If the Request class had a lot more methods, using method_missing might have been an irresponsible design choice. But since the notion of the request and the thing being requested are necessarily intertwined, this approach made a lot of sense to me because it let me join the two classes up for usage (WebEx::Request and WebEx::XMLObject), improving readability and reducing verbosity but still letting me keep the two classes usefully separate in the code.

method_missing is one of those things that you’ll sometimes hear people talking about Ruby referring to as magic. Like other, more experienced folk, I hate the usage of the word “magic” to describe things that happen in Ruby. The concepts behind metaprogramming aren’t that difficult, and as Mr. Giles says in his blog post, metaprogramming is just programming. If not for method_missing, you wouldn’t have cool stuff like Builder and RSpec. Or maybe you would, but not as elegantly. method_missing underlies much of the powerful Doman-Specific Language stuff that is so popular and useful in Ruby, so as my dad used to say: “it’s not bad or scary — it’s just different.”

(Gist of the full module)

Leave a Reply