Introduction
Validations are your application's first line of defense for data integrity. They ensure that only valid, well-formed data gets saved to your database, providing automatic error messages when something is wrong.
Key Concepts
- Validation: A rule defined in a model that must pass before a record can be saved.
validates: The DSL method used to declare validations with built-in helpers.errors: AnActiveModel::Errorsobject attached to every model instance, populated when validation fails.- Conditional validation: Validations that only run when certain conditions are met.
Real World Context
Without validations, your database could contain empty emails, negative prices, or duplicate usernames. Validations catch bad data at the application level before it reaches the database, providing user-friendly error messages instead of cryptic database errors.
Deep Dive
Basic Validations
rubyclass Article < ApplicationRecord validates :title, presence: true validates :body, presence: true, length: { minimum: 10 } end article = Article.new(title: "") article.valid? # => false article.errors.full_messages # => ["Title can't be blank", "Body can't be blank"]
Common Validation Helpers
ruby# Presence validates :title, presence: true # Length validates :title, length: { minimum: 5 } validates :bio, length: { maximum: 500 } validates :password, length: { in: 8..72 } # Uniqueness validates :email, uniqueness: { case_sensitive: false } # Format validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } # Numericality validates :price, numericality: { greater_than: 0 } validates :quantity, numericality: { only_integer: true } # Inclusion validates :status, inclusion: { in: %w[draft published archived] }
Conditional Validations
rubyclass User < ApplicationRecord validates :password, presence: true, if: :password_required? validates :card_number, presence: true, if: -> { payment_type == 'card' } private def password_required? new_record? || password.present? end end
Custom Validations
rubyclass Article < ApplicationRecord validate :publish_date_cannot_be_in_past private def publish_date_cannot_be_in_past if publish_date.present? && publish_date < Date.today errors.add(:publish_date, "can't be in the past") end end end
Working with Errors
rubyarticle = Article.new article.valid? # => false article.errors[:title] # => ["can't be blank"] article.errors.full_messages # => ["Title can't be blank"] article.errors.count # => 1
Common Pitfalls
- Relying only on client-side validation: Always validate on the server. Client-side validation can be bypassed.
- Not checking
valid?before using errors: Theerrorscollection is only populated after callingvalid?,save, orcreate. - Uniqueness race conditions:
validates :email, uniqueness: truecan have race conditions. Add a unique database index as a safety net.
Best Practices
- Pair model-level uniqueness validations with database-level unique indexes.
- Use
presence: trueon all required fields. - Write custom error messages that are helpful to end users.
Summary
- Validations run before
save,create, andupdate, preventing invalid data from being persisted. - Built-in helpers:
presence,length,uniqueness,format,numericality,inclusion. - Use
errors.full_messagesto get human-readable validation failure descriptions. - Custom validations use
validate :method_namewitherrors.addfor custom rules. - Always back uniqueness validations with database indexes.
Code Examples
ruby
class Article < ApplicationRecord
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true
validates :status, inclusion: { in: %w[draft published archived] }
end
article = Article.new(title: "")
article.valid? # => false
article.errors.full_messages # => ["Title can't be blank", ...]