A dive into the sharp knives

On one of my favorite parts of Ruby and the difference between a knife, an axe and a scalpel.

Jérémie Bonal
6 min readFeb 16, 2018
More knives than you know what to do with.

I’ve been a developer for a few years now, I’ve used my share of languages but truly, none has found its place in my heart quite as deep as Ruby.

When I started the Web Development course that would lead me to Ruby, I remember betting the course’s manager that “Nah, I’m pretty sure I’ll still use Python in my day to day life after this”. 9 weeks later, I was out a bottle of Japanese whisky.

There are two main criticisms that are thrown in the face of Ruby, usually: it is slow, and it is dangerous to let people play with if you want to keep your codebase sane.

This article won’t talk about the first point, more intelligent people than me have already covered the subject in detail.

I’m gonna be talking about the “sharp knives” I’ve seen some developers rail against.

They’re one of my fave things.

The cilantro is the task you need to do, the fingers are your codebase.

Knives

The “sharp knives” are referring to the whole bunch of meta-programming available in Ruby and the fact that everything is pretty much open to be changed and redefined at any point.

Did you know that in Ruby you can set up stuff to happen when a method is not found? Or whenever a method is called, no matter where from or on what object? Or even whenever a line of code is executed?

The first one is especially common in Ruby code. For example, here is a quick implementation of the Decorator pattern:

And since Ruby is awesome, you can use the standard library’s SimpleDelegator and not even need to implement that.

In a mere 11 lines, we’ve got a wrapper object that still has the same interface as the object it wraps around and adds some fancy new behavior around the method calls.

It can also be used for some other fanciful stuff, too. Let’s say… a Hash whose contents can be accessed through method calls?

This is pretty tame in my opinion, but some people are still firmly opposed to this mechanism — despite how much nicer the syntax can gets when properly used. For example this is a code excerpt using the popular testing framework RSpec:

Out of this the only ruby keywords are “do”, “end”, “nil”, ‘:’, the quotes, the parens and the braces.

Pretty clean isn’t it? If you add to that the hooks to execute code on module inclusion, class inheritance and the ability to programmatically add methods you might end up with something looking like this:

This actually comes from the codebase I spend my weekdays on.

There’s so many keyword-looking efficient calls here you’d never guess how many things this class does.

Let’s see something sharper.

Heads will fall.

Axes

The points that really irks people is not even the hooks, but the rather the openness of everything. I assume at that point that those who disagree about using what we’ve seen above are simply fuming.

In Ruby everything can be redefined. For example:

This process is commonly referred to as monkey-patching. (The name is a variation on guerilla-patching →gorilla→monkey) and some people really don’t like that. Why? Because here’s what they see:

Truly, this would be awful. And it’d mean you couldn’t trust any library anymore. As an exercise left for the reader I’ll let you ponder on how you can trust any library to do what it says on the label, even in not so open languages.

Fact is, nobody in their right mind would write such a library, nobody even of a less sound mind would use it without a single thing to vouch for it. Nefarious (or even incompetent) monkey-patching is, in practice, way less of a thing than some expect.

But anyhow, let’s soldier on and see if we can go even further:

This will pretty much instantly crash your Ruby REPL.

This little snippet removes every single method after it’s been called. Your whole program becomes a single-use codebase. And obviously, it often ends up crashing pretty fast.

You’ll note the use of send allowing us to even ignore what’s been set as private and call whatever we want. Encapsulation is nice but nothing is sacred in Ruby.

I do see that so far my examples in this section don’t exactly show how useful Ruby metaprogramming is. I’ve focused on the sharpness of the knives but in fact, the most common use of metaprogramming often rather looks like:

This is from ActiveSupport, a popular library of multipurpose tools and useful monkey patches.

Capabilities extension. Also very common in the wild is straight-up fixing up a bug in a library until the bug-free version is officially released.

If the first examples were knives, useful on a day-to-day basis and very multipurpose, these are more like axes or machetes — useful but on a case-by-case basis where there’s gonna be some heavy use around it. Also similar to axes in that they can be dangerous in theory but most people that wield them know what they are doing.

But what’s even sharper?

Precision work.

Scalpels

At Ekylibre, we use a library called ODFReport which allows us to generate ODF documents from a template programmatically, including generating tables by iterating over data.

But the fun fact is that in nested iterations, the deepest construction loops do not have access to the data used to generate their “parent” loops. But we needed to do just that if we were to present clean and crisp documents during the demo we were having. So I set out to find a way to add that.

It didn’t need to be clean, it didn’t need to be the best way to do it, it needed to work by the end of the afternoon.

I could’ve have brought out the axe and monkey-patched over several methods to adjust the interface to my needs, but in a 800 lines of code long library, this would’ve meant rewriting most of it, and it was a lot of tedious work.

So after reading the code for a while, I identified exactly where was the data I needed and…

Little cheating here, this is not pure Ruby since it uses the binding_of_caller gem.

This looks around for a variable named data_item and if it doesn’t find it, goes up to the caller of the current context, just like you would when using a debugger.

The loop was added because the number of steps “up” actually changed depending on where I was when I needed to fetch the object.

This couldn’t be decently used in a library, or even in a heavy-use part of the app. And we’re planning on forking the library soon (or contributing to it if a maintainer is there to accept our PRs). But it did the work, took me a handful of minutes to code and is working perfectly so far.

Nobody would bring a scalpel to write a book, at a dinner, or to chop down trees. But boy if it isn’t useful when you need to do some precision work.

If you’ve had some fun with those sharp knives and have found some cool things you can do with them, hit me up! I’m down to talk about them. Or about why not to use them, I’m more than happy to exchange opinions if you want to bring up a point I’ve overlooked.

--

--