-
Purpose: Ensure that only valid data is saved in the database.
-
validates :name, presence: true
- This checks if the
name
attribute is provided before saving aPerson
object.
-
-
valid? Method:
-
Runs validations and returns
true
if no errors are found. -
Person.create(name: "John Doe").valid? # true Person.create(name: nil).valid? # false
-
-
Ensures Data Integrity
: Ensures only valid data gets saved to the database. -
Database-Agnostic
: Model-level validations can’t be bypassed like client-side validations can. -
Built-In Helpers
: Rails provides helper methods for common validation checks (e.g.,presence
,uniqueness
). -
Comparison
:- Database Constraints: Useful for ensuring things like uniqueness but can be hard to test and maintain.
- Client-Side Validations: Can be unreliable if JavaScript is disabled.
- Controller-Level Validations: Can make the controller unwieldy, so it's better to keep validations in the model.
-
Object Creation
: When a new object is created usingnew
, it doesn’t belong to the database yet. -
Save Operation
:- Before Save: Validations happen before the object is saved to the database. If any validation fails, the save will not occur.
- Creating:
create
triggers validations and attempts to insert a new record into the database. - Updating:
update
triggers validations for existing records.
-
Methods That Trigger Validation
:create
,create!
,save
,save!
,update
,update!
trigger validations before saving.- The “bang” methods (
create!
,save!
,update!
) raise an exception if validation fails, while non-bang versions returnfalse
.
- These methods bypass validations and save invalid data to the database. Use them with caution:
-
decrement!
,increment!
,insert
,update_all
,update_attribute
, etc. -
save(validate: false) # skips validations
-
# skipping validation with update_attribute person = Person.create(name: "Valid Name") person.update_attribute(:name, nil) # Skips validation and updates to invalid state
-
-
valid?: Returns
true
if no errors were found during validation, otherwise `false``. -
invalid?: The opposite of
valid?
; returnstrue
if errors were found. -
errors: Provides access to validation errors for a model object.
-
p = Person.create p.errors.full_messages # ["Name can't be blank"]
-
-
Accessing Errors:
- You can access errors for a specific attribute using
errors[:attribute]
. -
Person.new.errors[:name] # returns an array of error messages for `name`
- You can access errors for a specific attribute using
-
valid?: Triggers validations and returns
true
if no errors are found,false
otherwise. -
invalid?: Returns
true
if errors are found. -
errors: Access errors related to the object.
-
errors[:attribute]: Check errors for a specific attribute.
-
Create & Save Methods:
create
andsave
trigger validations before saving.create!
andsave!
raise exceptions when validations fail.- Methods like
insert
,update_all
skip validations and should be used carefully.
-
Ensures that a checkbox (e.g., terms of service agreement) is checked.
-
validates :terms_of_service, acceptance: true
-
Validates that two fields have the same value (e.g., password confirmation).
-
validates :email, confirmation: true validates :email_confirmation, presence: true
-
Validates that one attribute is compared to another (e.g., start_date must be earlier than end_date).
-
validates :end_date, comparison: { greater_than: :start_date }
-
Ensures that an attribute matches a specific regular expression pattern (e.g., email format).
-
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, message: "only allows letters" }
-
Ensures that the attribute value is included in a given set of values (e.g., predefined list of options).
-
validates :size, inclusion: { in: %w(small medium large), message: "%{value} is not a valid size" }
-
Ensures that the attribute value is not included in a given set (e.g., reserved usernames).
-
validates :subdomain, exclusion: { in: %w(www us ca jp), message: "%{value} is reserved." }
-
Validates the length of an attribute (e.g., string length constraints).
-
validates :name, length: { minimum: 2 } validates :password, length: { in: 6..20 }
-
Ensures that an attribute contains only numeric values.
-
validates :points, numericality: true
-
Ensures that an attribute is not empty or nil.
-
validates :name, :login, :email, presence: true
-
Ensures that an attribute is empty or nil.
-
validates :name, :login, :email, absence: true
-
Ensures that an attribute's value is unique in the database.
-
validates :email, uniqueness: true
-
Used when model has associations that always need to be validated.
-
validates_associated :books
- Skips validation if the attribute is
nil
.
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }, allow_nil: true
end
Coffee.create(size: nil).valid? # => true
Coffee.create(size: "mega").valid? # => false
- Similar to
:allow_nil
, but skips validation for blank values (nil
or empty string""
).
class Topic < ApplicationRecord
validates :title, length: { is: 5 }, allow_blank: true
end
Topic.create(title: "").valid? # => true
Topic.create(title: nil).valid? # => true
-
Allows specifying a custom error message when validation fails.
-
Supports placeholders like
%{value}
,%{attribute}
, and%{model}
.
class Person < ApplicationRecord
validates :name, presence: { message: "must be given please" }
validates :age, numericality: { message: "%{value} seems wrong" }
end
- Can also accept a
Proc
:
class Person < ApplicationRecord
validates :username,
uniqueness: {
message: ->(object, data) do
"Hey #{object.name}, #{data[:value]} is already taken."
end
}
end
-
Specifies when the validation should be executed.
-
Default: Runs on both
create
andupdate
. -
Options:
on: :create
→ Runs validation only when creating a new record.
on: :update
→ Runs validation only when updating a record.
- Custom contexts can be defined and explicitly triggered.
class Person < ApplicationRecord
validates :email, uniqueness: true, on: :create
validates :age, numericality: true, on: :update
validates :name, presence: true
end
person = Person.new(age: 'thirty-three')
person.valid?(:account_setup) # => false
person.errors.messages # => {:email=>["has already been taken"], :age=>["is not a number"]}
- Custom contexts:
class Book
include ActiveModel::Validations
validates :title, presence: true, on: [:update, :ensure_title]
end
book = Book.new(title: nil)
book.valid?(:ensure_title) # => false
book.errors.messages # => {:title=>["can't be blank"]}
- When using a custom context, it also includes validations without a context:
class Person < ApplicationRecord
validates :email, uniqueness: true, on: :account_setup
validates :age, numericality: true, on: :account_setup
validates :name, presence: true
end
person = Person.new
person.valid?(:account_setup) # => false
person.errors.messages # => {:email=>["has already been taken"], :age=>["is not a number"], :name=>["can't be blank"]}
- Raises
ActiveModel::StrictValidationFailed
when the object is invalid.
class Person < ApplicationRecord
validates :name, presence: { strict: true }
end
Person.new.valid?
# ActiveModel::StrictValidationFailed: Name can't be blank
- Custom exception with
:strict
:
class Person < ApplicationRecord
validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
Person.new.valid?
# TokenGenerationException: Token can't be blank
- Associates a validation with a method that determines when it should run.
class Order < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
- Allows inline conditions instead of defining separate methods.
class Account < ApplicationRecord
validates :password, confirmation: true,
unless: Proc.new { |a| a.password.blank? }
end
- Using a lambda for inline conditions:
validates :password, confirmation: true, unless: -> { password.blank? }
with_options
helps group multiple validations under a single condition.
class User < ApplicationRecord
with_options if: :is_admin? do |admin|
admin.validates :password, length: { minimum: 10 }
admin.validates :email, presence: true
end
end
- All validations inside the block are applied only if
is_admin?
is true.
- Multiple conditions can be used together with an array.
class Computer < ApplicationRecord
validates :mouse, presence: true,
if: [Proc.new { |c| c.market.retail? }, :desktop?],
unless: Proc.new { |c| c.trackpad.present? }
end
- The validation only runs when all
:if
conditions are met and none of the:unless
conditions are true.
-
Create a class inheriting from
ActiveModel::Validator
. -
Implement the validate method.
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.start_with? "X"
record.errors.add :name, "Provide a name starting with X, please!"
end
end
end
class Person < ApplicationRecord
validates_with MyValidator
end
-
Define methods that validate model attributes and add errors.
-
Register methods using validate.
class Invoice < ApplicationRecord
validate :expiration_date_cannot_be_in_the_past,
:discount_cannot_be_greater_than_total_value
def expiration_date_cannot_be_in_the_past
if expiration_date.present? && expiration_date < Date.today
errors.add(:expiration_date, "can't be in the past")
end
end
def discount_cannot_be_greater_than_total_value
if discount > total_value
errors.add(:discount, "can't be greater than total value")
end
end
end
- Use validators to list all validations.
Person.validators
Person.validators_on(:name)
- Provides access to all validation errors.
class Person < ApplicationRecord
validates :name, presence: true, length: { minimum: 3 }
end
person = Person.new
person.valid?
person.errors.full_messages #=> ["Name can't be blank", "Name is too short (minimum is 3 characters)"]
- Retrieves errors for a specific attribute.
person.errors[:name] #=> ["can't be blank", "is too short (minimum is 3 characters)"]
- Filters errors based on attributes and types.
person.errors.where(:name, :too_short) #=> [...]
- Manually adds errors.
errors.add :name, :too_plain, message: "is not cool enough"
- Adds errors not tied to a specific attribute.
errors.add :base, :invalid, message: "This person is invalid because ..."
- Returns the total number of validation errors.
- Clears all errors but does not make an object valid.
person.errors.clear