Summary: Dirty checking is a way of tracking changes by checking a variable’s value against what that variable’s value was. The Ruby on Rails
ActiveModel::Dirty module enables dirty checking. This is useful for running ActiveRecord callbacks only when an attribute of an active model has changed.
I first heard the phrase “dirty checking” only recently, as I began exploring front end frameworks. Dirty checking is inherent to frameworks like Angular JS, in which a listener asynchronously checks its value against its previous value (thus knowing if something has changed and needs to be re-rendered).
Little did I know that dirty checking is possible (and valuable) in Ruby as well.
(To skip the background context and jump straight to the technical explanation, click here).
In a recent pairing session, I was asked to implement a feature for a Q&A polling application. Imagine the application depicted below:
Now imagine yourself on the flip side, not as a user answering a poll, but as an admin at the portal implementing a poll, creating new questions, and populating prospective choices. This was the context for my challenge.
The challenge broke down into two parts:
Front end: Add a couple options, in addition to populating the required choices with text.
- A ‘randomize’ checkbox, which would mix up the order in which choices displayed.
- A ‘special choices’ option, which could either be ‘Other’ or ‘None of the Above’.
- Back end: Reflect any changes by persisting to the database.
The front end feature was straightforward enough: add a checkbox option for randomization, and a radio button. Something as simple as the above right.
Imagine the class below. In creating an answer as an administrator, we create an answer object with multiple choices (take my word for it that there was a lot more going on than this):
1 2 3 4 5 6 7 8
We need to add an attribute for
:special_answer—easy enough. But how do we ensure that our new
special_answer becomes an element in the array of our answer object’s
choices? A simple append method. And how do we ensure things are saved as necessary? A simple callback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
One problem:each time we change the special answer, we’ll get an additional answer.
There should only ever be one special answer, so we need the new special answer to replace the previous one rather than continuing to add additional ones. And that’s a job that dirty checking can handle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
The difference here? That simple
if: special_answer_changed? following the callback, as well as
include ActiveModel::Dirty. The ActiveModel::Dirty module gives us a few key methods, most notable
changed?. As the code above illustrates, we can make the callback conditional by dirty checking whether the
special_answer attribute has changed.
You can view the module’s documentation in its entirety here. By its own definition, it “Provides a way to track changes in your object in the same way as Active Record does.” This way of tracking changes is helpful to us in this case—performing a callback only if the attribute has changed—and likely has applications in many other situations.