(I sent this post to my list a few months back. If you like it, and want to read more like it, you should sign up!)
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?
if
and unless
?
Validations support :if
and :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?)
Beyond saving
Custom validation contexts are useful for more than just saving a record in different ways. Contexts work with valid?
:
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 true
or false
:
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 :if
or :unless
on your validations. But like a lot of the stuff in Rails, custom validation contexts are a good compromise between readability and flexibility.