Sometimes want to skip certain validations on your database models. Maybe you have a multi-step wizard or want admins to have more freedom in changing data.
You might be tempted to have certain forms skip validations, but there is a better way.
Rails allows you to pass in a context
when saving or validating a record. You can combine context
with the on:
option to run only certain ActiveRecord validations.
Usage
Let’s say you want want to build a multi-step workflow for a job board. You might allow someone to create a job listing and start filling in the data, but not fully validate everything until it is time to publish the listing.
class Listing < ApplicationRecord belongs_to :company belongs_to :user has_rich_text :requirements validates :title, presence: true, length: { maximum: 50 } validates :salary_range, presence: true, on: :publish validates :application_instructions, presence: true, on: :publish def publish! self.published_at = Time.current save(context: :publish) end end
In this case, we will always require a title
(with 50 characters max) whenever we create or edit this record. But we will only validate salary_range
and application_instructions
when we pass :publish
as the validation context.
You could implement this workflow with controller actions like:
class ListingsController < ApplicationController def create @listing = Listing.new(listing_params) if @listing.save redirect_to @listing, notice: "Listing created" else render :new, status: :unprocessable_entity end end def publish @listing = Listing.find(params[:id]) if @listing.publish! redirect_to @listing, notice: "Listing published" else render :edit, status: :unprocessable_entity end end end
You can also add validations that are different based on which user is making the change. Maybe you want to allow admins to give special, short usernames to your friends.
Here we can set one rule that requires six character usernames in the :create
context (which Rails will include by default when creating a record). Then we add a rule in the :admin
context that only requires three characters.
class Account < ApplicationRecord validates :username, length: { minimum: 6 }, on: :create validates :username, length: { minimum: 3 }, on: :admin end Account.new(username: "swanson").valid? # => true Account.new(username: "swanson").valid?(:admin) # => true Account.new(username: "mds").valid? # => false Account.new(username: "mds").valid?(:admin) # => true Account.new(username: "a").valid? # => false Account.new(username: "a").valid?(:admin) # => false
One downside with using Rails validation contexts is that you may not be able to use database level validations. Being able to persist partially-valid records or have conditional rules is a powerful feature, but it’s not without costs.
Think carefully about moving validations from the database constraint level to your application.
Additional Resources
Rails Guides: Validations :on option
Rails API Docs: ActiveRecord::Validations#valid?
Top comments (0)