In the past few weeks, I’ve had a few conversations with web developers and back-end engineers who are unfamiliar with Ruby, in which I’ve tried to explain how Ruby can be nearly as expressive as Perl (tiny amounts of code can accomplish a lot) while being as readable as Python or Java. In fact, I think that Ruby’s expressiveness can remove distracting boilerplate code, allowing compact expressions to be far more readable than a more verbosely written version of the same algorithm.
Sadly, if you are not a Ruby developer, this may sound rather suspicious — what is this, cult gibberish? “As expressive as Perl without being hard to read? Nonsense! The only way to make readable code is to use Java, and to write 5x as many lines as is absolutely necessary, embedding a comment before every line and a Javadoc with as many lines of explanation as the code in the method.” I disagree; I think code should be self-documenting, and that it should be clear to a programmer familiar with that language what the code is doing, without the need for inline comments except in rare cases.
Well, a few months ago Ilya Grigorik posted a Gist containing a really amusing microframework that is one of the most brilliantly expressive bits of code I’ve ever seen, without being particularly hard to read. 13 lines of Ruby code provides a web application that will allow any Ruby object you choose to have its entire public API exposed via URLs of the form /method/arg1/arg2, where the method return value becomes the response body.
For the benefit of non-Ruby developers, I’m going to walk through all 13 lines of code plus the 1-line example application to explain how it works. I really hope that by reading through this explanation you will come to appreciate the expressiveness of Ruby, and maybe what a clever bit of dynamic language programming this is.
Here is the code: Inspired by @JEG2’s talk at Rubyconf… Any ruby object, as a webapp! ‘Cause we can. :-)
In particular, note the 7th comment, with the embedded animated GIF. This should put you into the right frame of mind for understanding this code.
Line 1: Load RubyGems, which is a library that helps you manage other Ruby libraries that your code depends on.
Line 2: Load Rack, which is a standard API for connecting web servers to web application frameworks.
Line 4: We are modifying the Object class itself, so our changes will affect every object in the application.
Line 5: We are adding an instance method called “webapp” to every object.
Line 6: We are going to modify the class of just the instance whose “webapp” method was invoked. This may seem paradoxical: how can you modify its class without affecting all other instances of that class? The answer is that we are actually creating a unique, anonymous subclass, and the object whose “webapp” method was invoked now becomes an instance of it. So when a caller invokes “webapp” on an object, we will modify just that instance.
Line 7: We are defining a method in our anonymous subclass named “call”, which takes a single argument, “env”. This is what the Rack API will invoke to ask our web application to handle each web server request.
Line 8: We are splitting the URL path on occurrences of “/” and storing the result into an array, and then filtering out all elements of that array that return true when the “empty?” method is called on them. Then, using multiple assignment, we take the first element of the array and assign it to the local variable “func”, and a new array containing the remaining elements to the local variable “attrs”.
Line 9: The Rack API expects a return value of an array containing 3 elements: an HTTP status code, a hash of HTTP response header names and values, and the response body. So here we respond with that array. “send(func, *attrs)” is an expression that calls “send” on self, which lets you invoke a method without specifying the name of the method nor the arguments to be passed to it until runtime. The method invoked is the one named in func, and the argument list passed to that method are taken from the attrs array. This is how the URL of the web request is mapped onto the API of the object that you made into a webapp.
Line 12: The return value from invoking the “webapp” method will be the same object that “webapp” was invoked on, which now has our special “call” method added.
Okay, so that’s it: a tiny Ruby web app microframework. Now we’re going to use it to expose an object!
Line 16: Start up the Mongrel web server listening on port 9292, and load up an empty array as a Rack webapp.
Lines 17-19 are a joke: because our webapp is an array, we have just implemented a horizontally scalable in-memory sharded NoSQL database, which is really the only suitable option for our expected ROFLScale needs.
Line 23: [].push(1) returns [1]; rack calls .to_s (convert to string) which returns “1” as the response body.
Lines 24 and 25: Since the array is the webapp, the changes are persistent.
Line 27: [1,2,3].to_a returns [1,2,3], and [1,2,3].to_s returns “123”.
Line 29: Remove the last value, returning 3 and leaving [1,2] in the array.
Line 30: Remove the first value, returning 1 and leaving [2] in the array.