Andy from Webcrunch

Subscribe for email updates:

Portrait of Andy Leverenz
Andy Leverenz

February 8, 2019

Last updated November 5, 2023

Understanding Ruby on Rails ActiveRecord Validations

Data is a great thing but the wrong data can be very very bad. Luckily, if you're a Ruby on Rails programmer you have a nice API of ActiveRecord validation helpers at your disposal.

This guide is an exploration of common and not so common ActiveRecord validations in a given Ruby on Rails application. Use it to help keep your databases happy!

Why use validations?

To ensure only valid data is saved to your database. On top of clientside validation, model-level validations provide an extra layer of security for any and all form fields within a Ruby on Rails application.

You can provide validations in a variety of levels but all have their strengths and weaknesses.

  • Database constraints - Unique constraints at the database level allows for no duplicate fields or columns if you require. This can go much deeper than just uniqueness. i.e. Default values, non-nil constraints, ranges, etc...
  • Client-side validation - These are useful visually and provide better user experience but are often more unreliable. I'd almost always provide a backup plan using this approach as one should.
  • Model Level (Rails) - What we are talking about now!
  • Controller Level (Rails) - Tempting to use but more difficult to test and maintain. It's a convention/best-practice to keep your controllers as light as possible.

When does validation actually occur?

Straight from the ruby on rails documentation:

There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, for example using the new method, that object does not belong to the database yet. Once you call save upon that object it will be saved into the appropriate database table. Active Record uses the new_record? instance method to determine whether an object is already in the database or not.

Creating a new Active Record class grants us an opportunity to use a new_record? method to figure this all out.

class Article < ApplicationRecord
end

In doing so we can hit rails console to pinpoint the process.

$ rails console

>> article = Article.new(title: "My Article")
=> #<Article id:nil title: "My Article", created_at: nil, updated_at: nil>
>> article.new_record?
=> true
>> article.save
=> true
>> article.new_record?
=> false

Actually calling save is what commits it to the database and behind the scenes creates the necessary SQL statements to do so. As a result, validations are typically run before sending data to the database entirely.

The following methods envoke validations upon use:

  • create
  • create!
  • save
  • save!
  • update
  • update!

The bang versions (e.g. save!) raise an exception if the record is invalid. The non-bang versions don't: save and update return false, and create just returns the object.

Methods that skip validations

Using any of the following will skip validations entirely. Use with caution on valuable data!

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

Validation helpers

What I love about Ruby on Rails is the bundled goodness you get for doing pretty hard things. Validation helpers are one of those things that make me feel warm and cozy when deploying a new app out in the wild. This provides a lot of support for a large range of data you may be collecting from the client-side.

Each helper accepts a :on and :message option. These options define when the validation should be run and what message should be added to an errors collection if it fails (you can use these to help inject what's wrong inside your views so the user knows what to change/alter).

There are a ton of validation helpers, below are some of my most used. Please check out the guide on active record validations for more use-cases.

Confirmation

Useful for validating when two text fields need to have the same entry. Think of an email confirmation for example.

class User < ApplicationRecord
  validates :email, confirmation: true
end

Now in your view you can have two fields:

<%= text_field :user, :email %>
<%= text_field :user, :email_confirmation %>

One gotcha here is that this validation only performs if the presence option is set to true in the model as well.

So the User model gets an update:

class User < ApplicationRecord
  validates :email, confirmation: true
  validates :email_confirmation, presence: true  
end

Exclusion

If you need to reserve any words or make sure a given entry is unique use exclusion.

class Profile < ApplicationRecord   
  validates :username, exclusion: { in: %w(username1 username2 username3), message: "%{value} has already been taken." }
end

Notice how I used :message here as well. This is provided on all validation helpers.

You can also use inclusion in the opposite manner. See the docs for more info!

Format

Formatting strings are sometimes ideal for your app. You can use regular expressions within validations to alter whatever you're after.

class Plan < ApplicationRecord
  validates :plan_type, format: { with: /\A[a-zA-Z]+\z/, message: "Plans can only inlude letters"}
end

Length

I use this one all the time to put a constraint on the character length of a given field.

class User < ApplicationRecord
  validates :name, length: { minimum: 5 } # sets a minimum
  validates :bio, length: { maximum: 500 } # sets a maximum
  validates :handle, length: { in: 5..16 } # provide a range
end

Feel free to personalize the messages with :message here as well. The following are all available to hook into to customize the message.

  • wrong_length
  • too_long
  • too_short

Example

class User < ApplicationRecord
    validates :bio, length: { maximum: 800, too_long: "%{count} characters is the maximum." }
end

Presence

This is probably the most widely used helper in my tool bag. Presence checks that fields are not empty.

class User < ApplicationRecord
  validates :name, :email, presence: true
end

You can also test an association exists on models

class Article < ApplicationRecord
  belongs_to :user
  validates :user, presence: true # looks for a user_id field on the article
end

On the opposing model, you can do the same with one catch called inverse_of

class User < ApplicationRecord
  has_many :articles, inverse_of: :user
end

Uniqueness

Maybe you want unique usernames on your app? Uniqueness helps with that.

class User < ApplicationRecord
  validates :username, uniqueness: true, { message: "Username already taken" }
end

This actually searches the User table for existing records to determine uniqueness

You can go further and limit the uniqueness down to a scope.

class User < ApplicationRecord
  validates :plan, uniqueness: { scope: :year, message: "can only update one per year"}
end

Conditional Validations

Sometimes you only need to validate within given conditions. You can perform conditionals using an approach like the following.

class Purchase < ApplicationRecord
  validates :card_number, presence: true, if: :paid_with_card?

  def paid_with_card?
    payment_type == "card"
  end
end

Need to group multiple conditions? You can do that

class User < ApplicationRecord
  with_options if: :is_admin? do |admin|
    admin.validates :location, length: { minimum: 10 }
    admin.validates :job_title, presence: true
  end
end

Custom Methods

Sometimes the helpers aren't enough and you might need something more custom. You can create your own methods that do some validations you roll yourself.

class Invoice < ApplicationRecord
    validate :active_subscriber, on: :create

    def active_customer
        errors.add(:customer_id, "is not active") 
        unless subscriber.active?
    end
end

Note here I'm validating this method using the :on option which lets you determine where to initialize the validation. In this case I chose :create.

You can also hook into the errors collection as I have to amend what outputs on the client-side if you need to. We don't have the :message on custom methods in this case. Read more about errors.

Final Words

Hopefully, validations have opened some ideas for you to make your app more constrained (in a good way). Using Ruby on Rails and all of its facets allows for safe data to enter any database of your choosing. From CSRF tokens to database-level validations, there are endless ways to enhance your app can remain calm about the data coming in.

Shameless Plug!

If you liked this post, I have more videos on YouTube and here on my blog. Want more content like this in your inbox? Subscribe to my newsletter and get it automatically.

Check out my course


https://i.imgur.com/KIaaJEB.jpg

☝ Want to learn Ruby on Rails from the ground up? Check out my upcoming course called Hello Rails.

Link this article
Est. reading time: 7 minutes
Stats: 8,503 views

Categories

Collection

Part of the Ruby on Rails collection