Active Record Validations

Kevin Randles
4 min readJan 16, 2018

--

Having spent a few too many years working in desktop support, if there’s one thing I can tell you about the users of your software, it’s that they’re going to use it incorrectly.

You probably won’t break the internet if you do this, but are you willing to take that risk?

As software engineers, part of our job is to make sure that the software we write doesn’t break when used in unexpected ways and, when users inevitably do something wrong, to do our best to communicate where they went wrong and how to correct it, otherwise, before long our software probably won’t have any users (or at the very least, we won’t have any happy users). Depending on the application, you might be able to limit the things your users can do such that there’s no way they can make your application do anything you don’t want them to do, but in most cases this just isn’t realistic.

Enter data validation

Building simple CLI apps at Flatiron recently, I’ve figured out some techniques for dealing with unexpected inputs and, as my applications have become more complex, I imagined the work involved in accounting for user error would increase at the same rate…but my code is on Rails now, and Rails does magic tricks.

Thankfully, the illusionists/engineers who built Rails have provided us with ways to take a lot of the work out of making sure our users are putting the right information in the right place, and in the right format, in the form of validation helpers. Active Record’s validation helpers cover a lot of cases we might find ourselves needing to account for.

Need to make sure the username field is filled out on the form for a new account? “Presence” is about as basic as validation gets — is there something here?

class User < ApplicationRecord
validates :name, presence: true
end
Person.create(name: "Beef Supreme").valid? # => true
Person.create(name: nil).valid? # => false

If so, great, Beef Supreme can create his account. If not, Rails, along with a little effort on our part, can tell Mr. Supreme that he forgot to enter his name. But, as anyone who knows Beef Supreme is aware, he’s not the sharpest, so what if he already has an account?

class Account < ApplicationRecord
validates :email, uniqueness: true
end

“uniqueness: true” will check to see if Beef has already registered his email address and, if so, generates an error message:

beef.supreme@brawndo.com has already been taken

I won’t go into all of the helpers here, but I do want to take a look at some of the ways you can extend their functionality beyond their basic syntax. Let’s say we have a simple Beer model, and our schema looks like this:

create_table "beers", force: :cascade do |t|
t.string "name"
t.string "brewery"
t.string "style"
t.float "abv"
t.text "description"
t.integer "vintage"
end

Validating that all of these fields have been filled out (to say nothing of other validations we might need to do) in our new beer form would get repetitious, and doing it like this doesn’t look very DRY:

class Beer < ActiveRecord::Base
validates :name, presence: true
validates :brewery, presence: true
validates :style, presence: true
validates :abv, presence: true
end

But this accomplishes the same thing:

validates :name, :brewery, :style, :abv, presence: true

If we want to check to see if a beer is already in our database, we can use “uniqueness”, but some brewers just name their beers after the style, and we might want to keep track of different vintages of the same beer separately.

validates :name, uniqueness: { scope: [:brewery, :vintage] }

Passing the “scope” option to our uniqueness validation allows us to check for a unique combination of properties, here ensuring that the same beer name, by a different brewery or of a different vintage, will be treated as a unique beer.

In other cases, the permitted values for one property may depend on the value of another. Here we can use conditional validations, for example, making sure the ABV of a beer is within the typical range, unless it’s a barleywine, in which case the sky’s the limit:

validates :abv, inclusion: { in: 0...30 },
unless: Proc.new { |b| b.style == "barleywine" }

Custom Validations

As you can see, Rails’ validation helpers are pretty versatile, and in many cases can provide everything we need to make sure we’re only accepting properly validated data, but Rails’ magic can’t account for every situation.

Custom validations are classes that inherit from ActiveModel::Validator (you can also write custom methods in your models, and call them using “validate :method_name”), allowing you to build whatever functionality you need into your checks, although Cueball’s issue illustrated above might be beyond help.

More on Active Record validations:

Rails Guides — Validations

Performing Custom Validations in Rails — an Example

The Perils of Uniqueness Validations

--

--