This blog post describes a design pattern for ensuring you consider every attribute in certain situations. I’ll use Ruby on Rails in the examples, but the general pattern should apply to any language or framework.
(I’m simplifying the data model for the sake of the example. It’s actually composed of more than one model, and doesn’t work precisely as described here.)
If a lot goes unsold, it may be relisted. Since we want to keep the history of listings, we make a copy.
So what should we copy? The title and description should be included. The highest bid amount should not, since the new listing won’t have any bids to start with.
Alright, we’ll write our code:
Then a few months later, someone adds an “artist” attribute. They forgot all about our relister, so relisted lots don’t carry it over as we would have liked.
We could instead list the attributes to exclude, but then if someone adds a new attribute and forgets to revise the relister, we would include that new attribute even if we shouldn’t.
The solution we settled on for situations like this is to list all includes and excludes.
When we relist, we go through every attribute. We include the ones we should, ignore the ones we shouldn’t, and raise an exception if we encounter a new attribute that we don’t know how to handle.
This is that rare thing, a perfect solution. We’re guaranteed that we can’t forget to declare how to handle a new attribute. If we do, we’ll be told.
If the relisting is covered by integrated tests at all, they will trigger these exceptions as soon as you add a new attribute and forget to declare it.
attributes_for_lot class method passes in the constants to the instance, to illustrate how it may work if you’re dealing with more than one model. In the real world, we have more than one model in place of
This also makes it very easy to test – and you can test it lightning-fast without loading Rails, if you have that set up.
This can be used for anything, of course. We’ve used it for relisting things and for cloning things more generally.
Most recently, we used it for reversing financial vouchers in an accounting system: basically, you create a copy but invert some of the numbers. For the copy part, we employed this pattern.