Dan Bernier bio photo

Dan Bernier

Lazy skeptic, asking dumb questions.

Email Twitter LinkedIn Github Stackoverflow

Tidy Your Attributes with TinySweeper

It can be easy to clean up your model attributes.

I hate seeing this:

1 2.2.0 :001 > Sundae.group(:topping).count
2  => {nil=>120, ""=>14, "hot fudge "=>2, "hot fudge"=>73, "butterscotch"=>33, "bourbon"=>4}

blank? makes it easy to gloss over the two kinds of non-values, but it’s still annoying. And it won’t help with those two kinds of hot fudge.

It’s easy for data to get this way. The most natural place to put this clean-up logic is in the writer attributes or before_validation hooks, but it’s tedious to remember them all.

Rich Hickey’s 2012 RailsConf keynote was titled “Simplicity Matters,” but at StrangeLoop, he called it Simple Made Easy. I like that title better: simplicity is more valuable to programmers, so let’s make simple solutions easy to implement.

What we need is a simple way to make cleaning attributes easy.

(How’s that for a setup?)

Enter TinySweeper: a gem that lets you define cleaning rules for any writer method on a ruby class.

Here’s how you use it - here’s the core idea of it:

 1 class Sundae < ActiveRecord::Base
 2   include TinySweeper
 3   sweep(:topping) { |t|
 4     if t.blank?
 5       nil
 6     else
 7       t.strip
 8     end
 9   }
10 end
12 dessert = Sundae.new
13 dessert.topping = "hot fudge "
14 dessert.topping #=> "hot fudge"
15 # and then...
16 dessert.topping = ""
17 dessert.topping #=> nil

You include the TinySweeper module, and define which fields should be cleaned, and how. The block sits in front of the writer method, intercepts values on their way in, sweeps them clean, and passes them on. That means you don’t have to save the Sundae for the topping to be cleaned.

There will be more to it, to make it easier to use, but it will all be built around that core idea. It’s only at version 0.0.4.

Prepending Modules

The first draft of TinySweeper was a bit inelegant: it would alias your writer method to something like “original topping=”1, and redefine it to clean up the value, and pass it to the original version. This method-juggling is effective, but it’s clumsy. And those “original foo=” methods show up in #methods.

Ruby 2.0 introduced us to the idea of prepending a module, which can help us out here. When you include a module, its methods are placed into the call-chain after the class, but before its superclass. Code like this:

1 module Extras
2 end
4 class Basics
5   include Extras
6 end

…sets up like this:

But if you prepend the module, its methods are placed into the call-chain before the current class:

So, if you prepend a module into your class, its methods can call super, and it’ll call the corresponding method on your class. This is just what TinySweeper needs - and in this commit, you can see the before-and-after effect on the code.

Lots of articles about prepending modules use memoization as the illustrating example, and it’s a good use case. But any time you need to do something before a method is called, or after, prepending a module might be a good idea. It’s a lot like Rails’ request filters. If you’d like to read more about it, Micah Woods has a good write-up.

Rolling Out TinySweeper

We’re just starting to use TinySweeper at Continuity, so it’s early, but I bet we’ll be updating it to make it easy to use in the near future. If you have ideas, pull requests are welcome!

  1. That’s right, methods with spaces in the name are legal - though you can’t call it with the dot syntax. Try making some with define_method.