Active Record Validations
Is a part of Active Record
Saving model in DB
m.new_record? # => true (it isn,t yet saved in the db)
m.persisted? # => false (counterpart of the above)
m.save # VALIDATES and saves model
Methods that trigger validations
m.valid?
m.invalid?
create # => the object to be created (if invalid it doesn't get saved)
create! # => exception (if invalid)
save # => false (if invalid)
save! # => exception (if invalid)
update # => false (if invalid)
update! # => exception (if invalid)
All other don't validate
Validations
validate :name, presence: true
# absence; phone_number and address must be empty if they are invited
validates :phone_number, :address, absence: true, if: :invited?
# validates checkboxes
validates :terms_of_service, acceptance: true
# checks if email matches email_confirmation (automatically creates virtual; attribute)
validates :email, confirmation: true # this alone wouldn't be enough because it would only check if it is not nil
validates :email_confirmation, presence: true, if: :email_changed?
Comparisons
class Promotion < ApplicationRecord
validates :end_date, comparison: { greater_than: :start_date }
end
| Options |
|---|
:greater_than |
:greater_than_or_equal_to |
:equal_to |
:less_than |
:less_than_or_equal_to |
:other_than |
Format (regex)
class Product < ApplicationRecord
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
message: "only allows letters" }
end
there is also the counterpart :without
In- and Exclusion
class Coffee < ApplicationRecord
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end
There is also the counterpart exclusion
Length
class Person < ApplicationRecord
validates :name, length: { minimum: 2 }
validates :bio, length: { maximum: 500 }
validates :password, length: { in: 6..20 }
validates :registration_number, length: { is: 6 }
end
Numericality
class Player < ApplicationRecord
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
end
Other Options
| Option | Description |
|---|---|
:greater_than |
Specifies the value must be greater than the supplied value. |
:greater_than_or_equal_to |
Specifies the value must be greater than or equal to the supplied value. |
:equal_to |
Specifies the value must be equal to the supplied value. |
:less_than |
Specifies the value must be less than the supplied value. |
:less_than_or_equal_to |
Specifies the value must be less than or equal to the supplied value. |
:other_than |
Specifies the value must be other than the supplied value. |
:in |
Specifies the value must be in the supplied range. |
:odd |
Specifies the value must be an odd number. |
:even |
Specifies the value must be an even number. |
Uniqueness
validates :email, uniqueness: true
There can only be one entry in the database with this value. There are also scopes and conditions.
Validate Associations
has_many :books
validates_associated :books
here the associations also get validated
Custom validations
class Person < ApplicationRecord
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, "must start with upper case") if /\A[[:lower:]]/.match?(value)
end
end
Inside another class
class AddressValidator < ActiveModel::Validator
def validate(record)
if record.house_number.blank?
record.errors.add :house_number, "is required"
end
if record.street.blank?
record.errors.add :street, "is required"
end
if record.postcode.blank?
record.errors.add :postcode, "is required"
end
end
end
class Invoice < ApplicationRecord
validates_with AddressValidator
end
Validation Options
validates :x, presence: true, allow_nil: true # obvious
validates :x, presence: true, allow_blank: true # same, but also "", etc.
validates :x, presence: { message: "%{attribute} in %{model}` cannot be %{value}" } # custom error message
On
# it will be possible to update email with a duplicated value
validates :email, uniqueness: true, on: :create
# it will be possible to create the record with a non-numerical age
validates :age, numericality: true, on: :update
# the default (validates on both create and update)
validates :name, presence: true
Conditional Validations
There are also the options :if and :unless.
class Order < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
You can also use procs or lambdas:
validates :password, confirmation: true, unless: -> { password.blank? }
Group Conditional Validations
class User < ApplicationRecord
with_options if: :is_admin? do |admin|
admin.validates :password, length: { minimum: 10 }
admin.validates :email, presence: true
end
end
Strict Validations
This way it always raises an exception
validates :name, presence: { strict: true }
Errors
person = Person.new
person.valid? # => false
person.errors.full_messages # => ["Name can't be blank", "Name is too short (minimum is 3 characters)"]
person.errors[:name] # same
person.errors.where(:name, :too_short) # => [...]
Error Objects
error = person.errors.where(:name).last
error.attribute # => :name
error.type # => :too_short
error.options[:count] # => 3
error.message # => "is too short (minimum is 3 characters)"
error.full_message # => "Name is too short (minimum is 3 characters)" (more readable)