Custom validation in Rails

February 15, 2018

Hey there! The winds have blown me in a new direction and this site is now just an archive for posterity's sake. All the new action will be happening at StayWildGames.com. I'll have a writeup detailing the move soon.

Here’s a little trick I like to do when I’m addressing validations in my models. Let’s say you have a standard Post model. It might look something like the following.

create_table "posts", force: :cascade do |t|
  t.string "title"
  t.text "body"
  t.integer "status"
  t.datetime "published_at"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

If you’re like me and prone to ideas popping into your head at inopportune times then you probably only have a few seconds to jot down an idea before you’re off to the next thing. So I tend to set up validations to facilitate my craziness.

# /app/models/post.rb
class Post < ApplicationRecord
  validates :title, presence: true
  validates_uniqueness_of :title, case_sensitive: false
  validate :body_on_publish
end

At first you might think to validate presence of the :body because: why would you ever publish a post without content? Well, if you’re chasing a two-year-old around who’s about to paint the dog pink then right now might not be the time to flesh out a post. You can only fill in the :title field and hit save before you have a pink dog.

So, we create a custom validation to solve this problem. The validate :body_on_publish is going to reference a private method where we can add a little logic.

# /app/models/post.rb
class Post < ApplicationRecord
  ...
  private

  def body_on_publish
    if status == 'published' && body.blank?
      errors.add(:body, "can't be blank")
    end
  end
end

This allows us to only perform a presence check if we’re actually about to publish our post. :status is simply an enum with ‘published’ as one of its values.

Now we can jot down a quick title as a note for later and only get bugged by a validation error if we’re actually trying to make something live.

Here’s the complete Post model for your convenience.

class Post < ApplicationRecord
  validates :title, presence: true
  validates_uniqueness_of :title, case_sensitive: false
  validate :body_on_publish

  enum status: {
    draft: 0,
    published: 1
  }

  private

  def body_on_publish
    if status == 'published' && body.blank?
      errors.add(:body, "can't be blank")
    end
  end
end