I ran into a weird situation the other day with one of my apps.
Say you have an
Article model, and these articles are first created as drafts. These drafts should be lightweight. You want to be able to create and edit them right away, without needing a body, or even a title.
But when you publish these articles, you need to have some extra validation. They should have a title, a body, and all the rest of that stuff.
You could write up a bunch of
if statements to do the checking yourself. But you wouldn’t have all the nice form handling you get from Rails. And I’ve found that going with Rails for the things Rails handles well will save you a lot of headache later on. Is there still a way to use validations for this?
:unless parameters, but this isn’t always so clean:
Besides, this isn’t the way I want to publish an article. I want to think about publishing an article as an action, not a state the article is in, so setting and checking an attribute isn’t the right answer.
A little-known, lightly documented Rails feature
This problem is a perfect fit for Rails’ validation contexts. I first learned about custom validation contexts from a gist DHH wrote. But it was hard to find a good explanation of what these were, and how to use them.
The documentation for custom contexts is pretty thin. The only mention of it I could find in the API docs was here. And the API docs for these validations don’t mention custom contexts at all! This makes it especially hard – if you don’t know what something’s called, how do you find more documentation on it?
I finally found a good overview here: http://blog.arkency.com/2014/04/mastering-rails-validations-contexts/. It’s worth a read. Let’s see what it looks like for our example:
Not too bad! The only weird thing is having to use
save instead of
update in the publish method. That happens because
update can’t take a validation context as a parameter. (Maybe that’s an opportunity for a pull request?)
Custom validation contexts are useful for more than just saving a record in different ways. Contexts work with
so you can use validation contexts anywhere you want to answer the question: “Does this object have this property? And if not, why not?”
DHH creates a method ending in
-able? that delegates to
valid?(:context)). I really like the look of that:
This way, when you check it, you get more information than just
Is the article viewable? Well, why not?
Just lightweight enough
There are a few other ways you could get this kind of validation behavior. You could build custom
ActiveModel service objects with their own validations. You could use
:unless on your validations. But like a lot of the stuff in Rails, custom validation contexts are a good compromise between readability and flexibility.