David Lesches

An Awesome Rails Validations Trick

So here's an awesome little Rails validation trick.

You are obviously familiar with the standard Rails validation usage.

class Post < ActiveRecord::Base

  validates :name, :body, presence: true


I'm sure you also know that validations can take an on option to specify that they should run only on create or only on update.

class Post < ActiveRecord::Base

  validates :name, :body, presence: true, on: :create


But here's the cool thing: you can actually specify a custom-context for on:

class Post < ActiveRecord::Base

  validates :name, :body, presence: true, on: :foo


And those validations only run when triggered explicitly: @post.valid?(:foo)

So Why Is This Cool?

So why is this cool? Because it will give you speedy tests on slow validations, such as a validation that depends on a 3rd-party API.

A classic example of this is a Card model for user's credit cards. In this hypothetical app, we are using Authorize.net CIM for card processing. When a user adds a card, a custom validation sends it off to Authorize.net to be validated and stored there. Within our own database, we only commit the last four letters of the card in order to stay (roughly) PCI-compliant.

class Card < ActiveRecord::Base

  # (this in obviously bare-bones code)

  # Associations
  belongs_to :user

  # Validations
  validates :type, :number, :month, :year, :cvv, presence: true
  validate :validate_credit_card

  # Callbacks
  before_create :trim_number

  # Methods
  def validate_credit_card
    credit_card.errors.each { |key, array| array.each { |value| errors.add(key.to_sym, value) } }

  def trim_number
    self.number = number[-4..-1]


  def credit_card
    @credit_card ||= ActiveMerchant::Billing::CreditCard.new(
      :number             => number,
      :month              => month,
      :year               => year,
      :verification_value => cvv,
      :brand              => brand


This works great... until we realize that our tests are crawling. Every test that needs to create a card resource is triggering the API call to Authorize.net.

The typical way out of this problem is to either stub the validate_credit_card repeatedly in our tests, or to create a separate service object to handle the Authorize.net API card creation.

Using the trick above though, we can solve this easily.

First, we add a custom context to the validation.

class Card < ActiveRecord::Base

  # ... other code omitted ...

  validate :validate_credit_card, on: :card_check

  # ... other code omitted ...


In our controller, we run the validation explicitly:

def create
  @card = current_user.cards.new(card_params)
  if @card.valid?(:card_check) and @card.save
    flash[:success] = 'Card created.'
    redirect_to @card.user
    flash[:error] = 'Error occurred, see below.'
    render :new

Our test suite, on the other hand, isn't triggering that Authorize.net API call on every card creation, because @card.valid?(:card_check) is not being run.


Kudos to @dhh who used this trick in one of his recent code gists.

Enjoyed this article? Let's Grab Coffee! We grab coffee with entrepreneurs all the time.
We'd love to meet you.


We are currently at capacity and not accepting quote requests.
Stay tuned for when we are open for projects.

Mmmmm, coffee.

We're fanboys of coffee meetings. Let's meet and chat about anything and everything.
Financier Patisserie
Blue Spoon Coffee
Mulberry & Vine
Starbucks Coffee
Kaffe 1668
Aroma Espresso