Skip to content

Instantly share code, notes, and snippets.

@zach-taylor
Last active August 21, 2018 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zach-taylor/3630f8e77309f8a7ac70580bab797792 to your computer and use it in GitHub Desktop.
Save zach-taylor/3630f8e77309f8a7ac70580bab797792 to your computer and use it in GitHub Desktop.

Back to Basics: Validations in Rails

rails new basics --skip-bootsnap --skip-bundle --skip-action-cable --skip-active-storage --skip-action-mailer --skip-yarn --skip-sprockets --skip-turbolinks --skip-coffee --skip-javascript --database=postgresql
rails db:setup db:migrate

Models

rails g model User name email bio:text age:integer states_visited:array 

Basics of rails validations

presence

validates :name, presence: true
validates_presence_of :name

chaining

validates :first_name, :last_name, presence: true

multiple

validates :name, presence: true, length: { minimum: 2 }
validates :name, presence: true, length: { minimum: 2, maximum: 255 }
validates :name, presence: true, length: { in: 2..255 }

format

EMAIL_REGEX = /\A\S+@.+\.\S+\z/
validates :email, format: { with: EMAIL_REGEX }

numericality

validates :age, numericality: { only_integer: true } 
validates :age, numericality: { only_integer: true, greater_than: 0, less_than: 1000 } 
validates :age, numericality: { only_integer: true, greater_than: 0, less_than: 1000, other_than: 69 } 

uniqueness

validates :email, uniqueness: true
validates :email, uniqueness: { case_sensitive: false }
validates :email, uniqueness: { scope: :name, case_sensitive: false }

options

validates :age, numericality: { only_integer: true }, allow_nil: true
validates :name, length: { in: 2..255 }, allow_blank: true
validates :name, length: { in: 2..255 }, allow_blank: true, on: :update
validates :email, uniqueness: true, if: :email_changed?
validates :email, uniqueness: true, unless: -> { Rails.env.test? }

Custom validations

validate

validate :states_visited_has_no_empty_elements

def states_visited_has_no_empty_elements
  return if states_visited.nil?
  return unless states_visited.include?(nil) || states_visited.include?('')
  errors.add(:states_visited, 'cannot contain nil or an empty string') 
end

ActiveModel::Validator

class ReadOnlyValidator < ActiveModel::Validtor
  def validate(record)
    record.errors.add(:base, 'The system is read only') if ENV['READ_ONLY']
  end
end

class User < ApplicationRecord
  validates_with ReadOnlyValidator
end

ActiveModel::EachValidator

class EmailValidator < ActiveModel::EachValidator
  EMAIL_REGEX = /\A\S+@.+\.\S+\z/
  def validate_each(record, attribute, value)
    return if value.nil?
    return if value.match? EMAIL_REGEX
    record.errors[attribute] << (options[:message] || 'format is invalid') 
  end
end

validates :email, email: true
validates :email, email: { message: 'needs to be a valid email address' }

Validation Contexts

on:

validates :bio, length: { minimum: 1 }, on: :admin
validates :bio, length: { minimum: 3 }, on: :user
validates :bio, length: { minimum: 3 }, on: [:user, :admin]

u = User.new(bio: 'a')
u.valid?(:admin)
u.valid?(:user)

Can be used with valid?, invalid?, and save.

ActiveModel::Errors

.errors

u = User.new(bio: 'hello!', name: 'Bob')
u.errors
u.valid?
u.errors

.errors[]

.errors[:attr].add / .errors[:attr] <<

.errors[:base]

.errors.messages

.errors.details

.errors.full_messages

.errors.clear

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment