I hated Rails helpers. I saw them as dumping grounds; one-off procedural aberrations in a sea of objects. But I didn’t just complain. I acted. I created the “frill” gem, an implementation of the decorator pattern that I extracted from a project that actually needed a decorator pattern in it’s view layer.
I had another project that seemed ideal for “frill”. It was a data analytics application that required a good deal of manipulation of the raw numeric data points for presentation.
For example, a typical stack of manipulations might look like:
- human size a number (for example, turn a “disk_space” attribute representing 1024 bytes into “1 KB”)
- if the underlying datapoint exists, render the human sized number, semantically and visually distinguishing the number from the units
- if the value is at this point not nil, render the number and its units red or green depending on whether or not it’s healthy
- if the underlying datapoint is nil, render a “N/A” message
We implemented the logic using “frill”. Our views looked like a paragon of simplicity:
= environment.disk_space
We had cleverly hidden all of the complexity in a series of modules that frill would dynamically decorate onto our models:
module HumanSizer
include Frill
def disk_space
HumanSizedNumber.new(super)
end
end
module Renderer
include Frill
after HumanSizer
def disk_space
if super
render “human_sized_number”, number: super
else
render “not_available”
end
end
end
#etc...
The frill library took care of stacking all of the decorators together, extending the objects at runtime with the relevant modules.
So what went wrong?
- Indirection. If these were the only two modules in the frills directory, then perhaps it wouldn’t have mattered. But when there was no obvious sign pointing from the view or controller to the relevant modules, you were often left scratching your head (or jumping into a debugger session) to determine the thread of execution when something went wrong
- Rigidity. The framework worked well for 90% of cases. But what about the other 10% of the time when you need to alter the presentation stack in some small but suble way? It turns out it was hard to remove existing decorations, or alter their presentation stack.
The latter problem proved especially tenacious. And lacing the decorators with all kinds of conditionals about the random one off cases where such and such decorator didn’t help anything.
HELPERS – STATELESS, COMPOSABLE, FLEXIBLE
When the frill library didn’t work out, we tried using helpers – and discovered that Rails helpers were the answer we’d been seeking all along. We created simple helper methods that we could stack together in pretty much any way our presentation demanded.
= report_NA_for_missing(colorized(human_sized(environment.disk_space)))
= colorized(percentage(environment.density_ratio))
Following the thread of decoration was now trivial.
It dawned on me that there’s really just a simple rule to follow with helpers: keep them stateless. If they’re simple, stateless methods that always return the same output for a given input, then they’re easy test and easy to stack in new and interesting ways. You can even create higher order functions quite easily by taking advantage of the “method” method for turning a method into an object:
= call_reporter_if_nil(method(:report_NA), colorized(human_sized(environment.disk_space)))
= call_reporter_if_nil(method(:report_unknown), colorized(percentage(environment.density_ratio)))
LET THE FUNCTIONAL PROGRAMMING REVOLUTION BEGIN
P.S. I don’t actually endorse that last code snippet.