Active Record Validations
Validations ensure that only valid data is saved to your database. They run automatically before save, create, and update.
When Validations Run
ruby
1# Validations are triggered by these methods:
2user.save
3user.save!
4user.create
5user.create!
6user.update(attributes)
7user.update!(attributes)
8user.valid?
9
10# These skip validations:
11user.save(validate: false)
12user.update_attribute(:name, "value")
13user.update_column(:name, "value")
14user.update_columns(name: "value")
15User.update_all(name: "value")Common Validations
Presence
ruby
1class User < ApplicationRecord
2 validates :name, presence: true
3 validates :email, presence: { message: "is required" }
4
5 # For boolean fields, use inclusion instead
6 validates :terms_accepted, inclusion: { in: [true] }
7endUniqueness
ruby
1class User < ApplicationRecord
2 validates :email, uniqueness: true
3
4 # Case insensitive
5 validates :email, uniqueness: { case_sensitive: false }
6
7 # Scoped uniqueness
8 validates :name, uniqueness: { scope: :organization_id }
9
10 # Custom message
11 validates :username, uniqueness: { message: "is already taken" }
12endLength
ruby
1class Article < ApplicationRecord
2 validates :title, length: { minimum: 5 }
3 validates :title, length: { maximum: 100 }
4 validates :title, length: { in: 5..100 }
5 validates :title, length: { is: 50 } # Exact length
6
7 # Custom messages
8 validates :bio, length: {
9 minimum: 10,
10 maximum: 500,
11 too_short: "must have at least %{count} characters",
12 too_long: "must have at most %{count} characters"
13 }
14endNumericality
ruby
1class Product < ApplicationRecord
2 validates :price, numericality: true
3 validates :price, numericality: { only_integer: true }
4 validates :price, numericality: { greater_than: 0 }
5 validates :price, numericality: { greater_than_or_equal_to: 0 }
6 validates :price, numericality: { less_than: 1000 }
7 validates :price, numericality: { less_than_or_equal_to: 1000 }
8 validates :quantity, numericality: { odd: true }
9 validates :quantity, numericality: { even: true }
10endFormat
ruby
1class User < ApplicationRecord
2 validates :email, format: {
3 with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i,
4 message: "must be a valid email address"
5 }
6
7 validates :phone, format: {
8 with: /\A\d{10}\z/,
9 message: "must be 10 digits"
10 }
11
12 validates :username, format: {
13 without: /\s/,
14 message: "cannot contain spaces"
15 }
16endInclusion and Exclusion
ruby
1class User < ApplicationRecord
2 validates :role, inclusion: {
3 in: %w[admin editor viewer],
4 message: "%{value} is not a valid role"
5 }
6
7 validates :username, exclusion: {
8 in: %w[admin superuser root],
9 message: "%{value} is reserved"
10 }
11endConfirmation
ruby
1class User < ApplicationRecord
2 # User must provide email_confirmation field
3 validates :email, confirmation: true
4 validates :email_confirmation, presence: true
5
6 # For passwords
7 validates :password, confirmation: true
8 validates :password_confirmation, presence: true
9endAcceptance
ruby
1class User < ApplicationRecord
2 # For checkbox fields (terms of service)
3 validates :terms_of_service, acceptance: true
4
5 # Custom acceptance value
6 validates :terms, acceptance: { accept: 'yes' }
7endConditional Validations
ruby
1class User < ApplicationRecord
2 # Only validate if condition is true
3 validates :phone, presence: true, if: :phone_required?
4 validates :company, presence: true, if: -> { role == 'business' }
5
6 # Unless condition
7 validates :nickname, presence: true, unless: :formal_name?
8
9 # Multiple conditions
10 validates :age, presence: true, if: [:adult?, :registration_complete?]
11
12 private
13
14 def phone_required?
15 role == 'admin'
16 end
17
18 def formal_name?
19 title.present?
20 end
21endOn Create or Update
ruby
1class User < ApplicationRecord
2 validates :password, presence: true, on: :create
3 validates :password, length: { minimum: 8 }, on: :update
4
5 # Custom context
6 validates :terms, acceptance: true, on: :checkout
7
8 # Usage with custom context:
9 # user.save(context: :checkout)
10endCustom Validations
Custom Method
ruby
1class Product < ApplicationRecord
2 validate :price_must_be_positive
3 validate :expiration_date_in_future, on: :create
4
5 private
6
7 def price_must_be_positive
8 if price.present? && price <= 0
9 errors.add(:price, "must be greater than zero")
10 end
11 end
12
13 def expiration_date_in_future
14 if expiration_date.present? && expiration_date <= Date.today
15 errors.add(:expiration_date, "must be in the future")
16 end
17 end
18endCustom Validator Class
ruby
1# app/validators/email_validator.rb
2class EmailValidator < ActiveModel::EachValidator
3 def validate_each(record, attribute, value)
4 unless value =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
5 record.errors.add(attribute, options[:message] || "is not a valid email")
6 end
7 end
8end
9
10# Usage in model
11class User < ApplicationRecord
12 validates :email, email: true
13 validates :backup_email, email: { message: "must be valid" }
14endValidates With
ruby
1class GoodnessValidator < ActiveModel::Validator
2 def validate(record)
3 if record.evil?
4 record.errors.add(:base, "This record is evil")
5 end
6 end
7end
8
9class User < ApplicationRecord
10 validates_with GoodnessValidator
11endWorking with Errors
ruby
1user = User.new(name: "", email: "invalid")
2user.valid? # => false
3
4# Check errors
5user.errors.any? # => true
6user.errors.empty? # => false
7user.errors.count # => 2
8
9# Get all error messages
10user.errors.full_messages # => ["Name can't be blank", "Email is invalid"]
11user.errors.messages # => { name: ["can't be blank"], email: ["is invalid"] }
12
13# Get errors for specific attribute
14user.errors[:name] # => ["can't be blank"]
15user.errors[:email] # => ["is invalid"]
16
17# Add custom error
18user.errors.add(:base, "Something is wrong")
19user.errors.add(:name, :blank) # Uses i18n key
20
21# Clear errors
22user.errors.clear
23
24# Check specific attribute
25user.errors.include?(:name) # => true
26user.errors.added?(:name, :blank) # => trueDisplaying Errors in Views
erb
1<%= form_with model: @user do |form| %>
2 <% if @user.errors.any? %>
3 <div class="error-summary">
4 <h2><%= pluralize(@user.errors.count, "error") %> prevented saving:</h2>
5 <ul>
6 <% @user.errors.full_messages.each do |message| %>
7 <li><%= message %></li>
8 <% end %>
9 </ul>
10 </div>
11 <% end %>
12
13 <div class="field">
14 <%= form.label :name %>
15 <%= form.text_field :name %>
16 <% if @user.errors[:name].any? %>
17 <span class="error"><%= @user.errors[:name].first %></span>
18 <% end %>
19 </div>
20<% end %>Combining Validations
ruby
1class User < ApplicationRecord
2 validates :name,
3 presence: true,
4 length: { minimum: 2, maximum: 50 }
5
6 validates :email,
7 presence: true,
8 uniqueness: { case_sensitive: false },
9 format: { with: URI::MailTo::EMAIL_REGEXP }
10
11 validates :password,
12 presence: true,
13 length: { minimum: 8 },
14 if: :password_required?
15
16 validates :age,
17 numericality: { only_integer: true, greater_than: 0 },
18 allow_nil: true
19
20 validates :website,
21 format: { with: URI::regexp(%w[http https]) },
22 allow_blank: true
23endAllow Nil and Allow Blank
ruby
1class User < ApplicationRecord
2 # Skip validation if value is nil
3 validates :age, numericality: true, allow_nil: true
4
5 # Skip validation if value is blank (nil, "", " ")
6 validates :website, format: { with: URI::regexp }, allow_blank: true
7endValidations are your first line of defense for data integrity!
