Validations in ActiveRecord

Bill Feng
5 min readJul 14, 2021

You’re developing the storage system for user data of a web service. You’ll need tables to keep track of each user’s name, bio, emails, and whatnot. With so much data involved, it’s critical to make sure that we have a robust system to guarantee it is “good” data being stored in our databases. What is “good” data? Well, for user’s emails, we want to make sure that we don’t get some gibberish like df034350fe2.gmail@com. Also, we want to make sure that it isn’t repeated. For the names of users, we don’t want any numbers in them. This brings us to validations, where we make sure that the data we store is properly formatted and valid.

The Naive Approaches

Let’s consider the different ways we can solve the problem of bad email addresses being stored in our database. The first line of defense is our html elements. The input element already can limit the type of data submitted in the html form. For example, with the following snippet, we will get a text field that checks for an @ symbol in order for the form to submit.

<label for="email">Enter your email:</label>
<input type="email" id="email" name="email">

This serves as a simplistic way to notify users that they might have messed up while entering their email addresses. Maybe they might have thought it was inputting their address or something…

Although this serves as a helpful tool in organizing the html file and providing a basic validation, our system is still vulnerable to improper data. The html code is client-sided, meaning that the end-user can change the html elements. Editing the html code, users could simply edit the snippet above to have a different input type.

<label for="email">Enter your email:</label>
<input type="text" id="email" name="email">

This would prevent the checking from happening, and we would receive garbage data in our controller. Not to mention, mischievous hackers could just directly create a request to our server with their junk values, as long as they knew the route. If the html was the only thing we implemented, we would be in trouble.

Well, then you might say, what if we have something to check when we receive the data? That would work but isn’t the best option. If we validated the data in the controller action, it may look something like this.

def create
valid = true
unchecked_email = params[:email]
email_split = unchecked_email.split("@")
if(email_split.length!=2)
valid = false
end

#some other conditions to check email

if (valid)
User.create(params)
else
render :new
end
end

This is a working solution since before the data is stored, users send it to the create route, where we check if it is valid. There is no other way to access the database, and so we succeed in preventing this data from being stored.

However, now consider what happens when we have our edit and update actions. If users have the option to change their email address, we’ll need to copy and paste all our validation code over. This doesn’t follow our DRY principles, so we use ActiveRecord’s built-in validations.

Active Record Validations

Active Record validations allow us to implement tests on our data before we store it. They are implemented in our class files, and as a result, they are automatically activated wherever we are in the program. Active Record validations are properties of Active Record, and not the database, meaning that the functions used to save data to the database are the ones that trigger the validations. Specifically, only the create, save, and update methods check for validity when they are called. A ! (bang) can be added to the end of the previous methods, and will also trigger the validations. The non-bang methods will return true if the action was successful, and false if there is a validation error. On the other hand, the bang-versions will raise an exception when there is a validation error. Therefore, typically the bang-versions of the Active Record functions are used for initial testing. Also, note that the new method doesn’t invoke validations, so we can use that to our advantage.

Simple Validations

Active Record has many built-in validation helpers that can be directly applied inside the class definition. These built-in validations can check the length or whether an attribute is one of a few selections. To use them, simply just use the validates keyword followed by the attribute symbol, and then the helper name and parameters. For example, to ensure that all emails are unique, the following code snippet would be inserted in the class definition for User.

validates :email, uniqueness: true

If the user we are trying to create has an email address that is already taken, when we try to use .save, the returned value will be false, and the action will not happen. If we wanted to verify that the email address is in the correct format, we can use the following snippet.

validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }

There are plenty of built-in validation helpers, but the point here is that they are easy to use. For a full list, check out the official documentation on validation helpers.

Custom Method Validations

What if we wanted to check something a bit more complicated, involving many attributes and some calculations? Suppose we have many user attributes, and we only check if the user has data for their profile only if they are opted-in to some service. Then, it may be difficult to implement this behavior only using the default helpers. However, fortunately, Active Record makes it easy to create custom validators. Before doing that, we need to understand how Active Record determines whether there is an error. Each Active Record object has an errors hash, which is initially empty. If validation fails, then a key-value pair is added describing the error. Before saving to the database, Active Record Checks if this hash is empty, and if it is, it means all the validations passed. So, when we create our own function, we just specify a key-value pair in the errors hash. So, all we need to do is create our function (this is a bit repetitive as it is covered by the length helper, it’s just a demo):

def bio_is_longer_than_5_characters
if (self.bio.length <= 5)
errors.add(:bio_length, "not long enough")
end
end

If the bio is not longer than 5 characters, we will add an error, which will result in the validations failing. To call this validation, we just need to use the keyword validate followed by the symbol method name. For example, to call the above validation method, we would use:

validate :bio_is_long_than_5_characters

As we can see, creating custom validation methods is pretty simple.

Handling Errors

To wrap things up, we just need to figure out how to handle the error that we receive. As a reminder, if the save or update action fails due to a validation error, it will return false, meaning in our controller, we can just alter the code to have different results depending on whether the returned value was true or not. So, instead, the create action will simply be:

def create
@user = User.new(user_params)
if(@user.save)
redirect_to @user
else
render :new
end
end

If the user successfully passes validations and saves, we are sent to the show page, and we re-render the form otherwise. For more examples and examples on how to display the errors in the views, consult the rails documentation here.

--

--