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.
