Pile of receipts one says not valid for travel

Duskfall Crew

Skip Validations in Specific Contexts with except_on

Rails 8.0 added except_on as an option to validations. It’s the inverse of on: and lets you skip validations in specific contexts.

Instead of…

…skipping all validations in your controller:

class Admin::UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save(validate: false)  # Skips ALL validations!
      redirect_to admin_users_path
    else
      render :new
    end
  end
end

…or using methods that bypass Active Record entirely:

class User < ApplicationRecord
  def self.admin_create(attributes)
    user = new(attributes)
    user.update_columns(attributes)  # No validations, no callbacks
  end
end

This example uses a class method on the model, but you might see similar code in something like a UserFactory or UserCreationService in your application.

Use…

class User < ApplicationRecord
  validates :birthday, presence: true, except_on: :admin_create
end

# Admin creates user without birthday
user = User.new(name: "Jane")
user.save(context: :admin_create) # => true

# Regular save still requires birthday
user.save # => false

You can also stack multiple contexts:

validates :email, 
  format: { with: URI::MailTo::EMAIL_REGEXP },
  except_on: [:admin_create, :bulk_import]

Extending this to callbacks

Validation callbacks get the same treatment in Rails 8.1.

before_validation :normalize_email, except_on: :quick_signup
after_validation :check_email_uniqueness, except_on: [:admin_create, :bulk_import]

Now your callbacks follow the same pattern as your validations. Consistency wins.

Why?

Your admin interface needs to create users without complete data. Your data import skips some business rules. Perhaps your API has different requirements than your web forms.

Previously, you’d handle this with dangerous workarounds, seperate “factory” objects, or repetitive code.

Why not?

Every skipped validation is a possible data integrity issue. But that’s also true of the other approaches.

Real database constraints can act as your safety net for true data consistency.

A form object or separate model might make more sense for your application or team but you might be fighting the framework.

If you are skipping validations (even conditionally) you should also take a moment to consider whether the validation is really required. Does your application break when there’s no data in that attribute or not? If it doesn’t then is there a need to validate at all?

except_on can keep validations concise while making intent clear: “validate everywhere except here.” Just remember: with great power comes great responsibility. And some folks will hate this as much as they hate callbacks.

Published on September 7th, 2025