If you’ve gone through a Rails tutorial like the Hartl Rails Tutorial or Rails For Zombies, you’ll be familiar with common model validations that prevent saving duplicate or incomplete records. Throughout this piece I’ll be talking about a Padawan
class, that might begin looking something like this:
1 2 3 |
|
This is a fine first step, but it only goes a small distance toward verifying any records we might try to save are actual valid Padawans. There is a conspicuous lack of business logic. Right now anyone with an age can be saved into the database, even those who are too old to begin the training.
Custom validations
If we dig in a little further with documentation and Stack Overflow answers we begin to learn about defining custom validators that implement attribute checks that are tailored to our application.
A custom validator is just a method that adds an error message if a given conditions is not met. A validator method that does not add any error messages is considered to have passed. So it’s simple to begin writing code like this:
1 2 3 4 5 6 7 8 |
|
We can fire up the console and quickly verify this does what we expect
1 2 3 4 5 6 7 |
|
Suck it, Luke Skywalker! You’re too old to be trained!
Not the refactor you were looking for
We’re successfully keeping the old fogies out of the Jedi Academy now, but plenty of people would look at this new code and be like
Woah, magic numbers! This is not the refactor you were looking for.
Actually, they probably wouldn’t say that. But they’d be thinking it while they downgrade their opinion of you. So let’s at least be a little more explicit with named constants and a bit of explanation for anybody who might work on this code after us.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Babies with lightsabers? Oh my!
If we wrote a few specs against this code, we’d realize that even babies in diapers are able to pass the validation, which doesn’t seem like a great idea. Imagine the trouble a baby with a lightsaber might get into! Or a toddler who can use the Force during tantrums! It won’t do.
Let’s modify our validator to check against a range of ages, and give the validator a more appropriate name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Now that we have our age requirements down, we remember that midichlorian_count
is also a factor when selecting Padawans for training. To borrow a useful piece of Harry Potter argot, we don’t want to waste time training squibs. So we’d better create a validation for midichlorians too.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
If we were writing a whole application, we could continue on adding validations in this way for quite a while. For very small apps we might never have a problem.
I felt a moderately-sized disturbance in the force
If you’re like me you might start to cringe as requirements increase and we find ourselves maintaining dozens of lines of validation code to do things as basic as checking whether a value falls within a range. As it is, we’re nearly up to 40 lines and all we’ve done is check that two properties fall within a particular range. Inconcievable!
As we write more and more validations, patterns begin to emerge. What’s that, you say? A disturbance in the Force? It’s as if a thousand methods cried out all at once to be DRYed up.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
Is this better? Now our code is even longer than before (!), but we can see how the savings from abstracting these range checks into methods would compound as we validate more attributes in the future.
This is a definite step in the right direction, but it’s beginning to look like this validation logic could probably be further generalized and be pulled into a Concern; there’s nothing inside attribute_less_than_or_equal_to
or attribute_greater_than_or_equal_to
that depends on Padawan
. If any other ActiveRecord models have similar types of validations—and we have every reason to believe that they would—we don’t want to duplicate and maintain that code in more than one place in our project.
A little knowledge is a dangerous thing
So what’s a Rails Padawan on the path to mastery to do in this situation? It turns out that the Rails developers have already solved this problem for us.
Custom validations are wonderful tools, but a little due diligence and study of the excellent Rails Guide on ActiveRecord validations at the beginning would have saved us a lot of effort in applying them in this situation. The docs show that ActiveRecord already ships with a large set of validation helpers that handle common scenarios like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
And check these out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
May the docs be with you!