We'll respond shortly.
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:
We implemented the logic using “frill”. Our views looked like a paragon of simplicity:
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?
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.
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.