(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:

app/models/article.rb class Article < ActiveRecord :: Base # validate in draft mode validates_presence_of :author_id # validate only when published validates_presence_of :body , unless: lambda { | o | o . draft? } ... end

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:

app/models/article.rb class Article < ActiveRecord :: Base # validate in draft mode validates_presence_of :author_id # validate only when published validates_presence_of :body , on: :publish ... end

app/controllers/article_controller.rb class ArticleController < ApplicationController respond_to :html def create @article = Article . create ( article_params ) respond_with @article end def publish @article = Article . find ( params [ :id ]) @article . attributes = article_params @article . save ( context: :publish ) respond_with @article end private def article_params params . require ( :article ). permit ( :body , :title , :author_id ) end end

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? :

@article . valid? ( :publish )

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:

app/models/article.rb class Article validate :has_been_published , on: :view validate :is_not_spam , on: :view def viewable? valid? :view end private def has_been_published if published_at . future? errors . add ( :published_at , "is in the future" ) end end def is_not_spam if is_spam? ( body ) errors . add ( :body , "has been detected as spam" ) end end end

This way, when you check it, you get more information than just true or false :

unless @article . viewable? # @article.errors is now filled out end

Is the article viewable? Well, why not?

Just lightweight enough