RoR Validations

Validations protect your database from bad data. They run before Rails saves a record and block the save if any rule is broken. A validation might check that a title is not blank, that an email looks correct, or that a price is greater than zero. Writing validations in the model means your data rules apply everywhere — from forms, the console, and background jobs alike.

Where Validations Live

Validations belong in the model file:

app/models/user.rb

class User < ApplicationRecord
  validates :name,  presence: true
  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :age,   numericality: { greater_than: 0, less_than: 120 }
end

How Validation Works

Controller calls: @user.save
        |
        v
ActiveRecord runs all validations
        |
        +-- All pass? ──────────────> Record saved to database ✓
        |
        +-- Any fail? ──────────────> Save blocked ✗
                                      Errors stored in @user.errors
                                      Controller renders form again
                                      User sees error messages

Common Validation Helpers

presence

Ensures the field is not blank:

validates :title, presence: true
validates :name,  presence: true

uniqueness

Ensures no two records share the same value:

validates :email,    uniqueness: true
validates :username, uniqueness: { case_sensitive: false }

length

Controls how long or short a value can be:

validates :title,    length: { minimum: 5 }
validates :bio,      length: { maximum: 500 }
validates :password, length: { in: 8..72 }
validates :zip_code, length: { is: 5 }

numericality

Ensures the value is a number:

validates :price,    numericality: { greater_than: 0 }
validates :quantity, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :rating,   numericality: { in: 1..5 }

format

Checks the value against a pattern:

validates :phone, format: { with: /\A\d{10}\z/, message: "must be 10 digits" }
validates :slug,  format: { with: /\A[a-z0-9\-]+\z/, message: "only lowercase letters, numbers, and dashes" }

inclusion and exclusion

validates :role,   inclusion: { in: %w[admin editor viewer] }
validates :status, exclusion: { in: %w[banned suspended] }

acceptance

Checks that a checkbox was ticked (common for terms of service):

validates :terms_of_service, acceptance: true

confirmation

Checks that two fields match (common for passwords and emails):

validates :password, confirmation: true
validates :email,    confirmation: true

Your form must include a password_confirmation field for this to work.

Custom Validation Methods

When a built-in helper does not cover your rule, write your own:

class Article < ApplicationRecord
  validate :title_cannot_contain_profanity

  private

  def title_cannot_contain_profanity
    banned = ["spam", "scam", "fake"]
    banned.each do |word|
      if title.downcase.include?(word)
        errors.add(:title, "cannot contain the word '#{word}'")
      end
    end
  end
end

Conditional Validations

Run a validation only under certain conditions:

validates :phone, presence: true, if: :contact_by_phone?
validates :bio,   length: { minimum: 50 }, unless: :draft?

def contact_by_phone?
  contact_method == "phone"
end

def draft?
  status == "draft"
end

Displaying Validation Errors in Views

When a save fails, Rails populates the errors object on the model. Display these errors in your form view:

app/views/users/new.html.erb

<% if @user.errors.any? %>
  <div>
    <h3><%= @user.errors.count %> error(s) prevented this form from saving:</h3>
    <ul>
      <% @user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

<%= form_with model: @user do |f| %>
  ...
<% end %>

Error messages appear automatically when the form re-renders after a failed save.

Validation Error Flow Diagram

User submits form
        |
        v
Controller: @user = User.new(params)
        |
        v
@user.save called
        |
        v
Validation runs: is email present? is email unique? is age valid?
        |
        +-- Pass ──> Save to database → redirect_to @user
        |
        +-- Fail ──> errors.add(:email, "is already taken")
                     save returns false
                     Controller: render :new
                     View shows the form again with errors displayed

Checking Validity Without Saving

user = User.new(name: "", email: "not-an-email")

user.valid?          # false
user.invalid?        # true
user.errors.full_messages
# ["Name can't be blank", "Email is invalid"]

user.errors[:email]
# ["is invalid"]

Use valid? in the console to test your validation rules without saving any data.

Skip Validations (Use Carefully)

In rare cases — like running a data import or seed script — you may need to bypass validations:

user.save(validate: false)
User.insert_all(data)          # Skips validations for bulk inserts

Only skip validations in controlled scripts where you have already verified the data. Never skip them on user-submitted data.

Well-written validations keep your database clean, protect against misuse, and guide users toward correct input with clear error messages.

Leave a Comment

Your email address will not be published. Required fields are marked *