Rails - Validations & Error Handling
jkap100
Posted on May 2, 2022
VALIDATIONS
One of the principal strengths of Rails is providing a structure on which to build a database, Rails offers an incredibly useful gem called Active Record. Active Record handles the M-model of the MVC paradigm, and is an ORM system that allows objects to be mapped to a database. In simple terms, Active Record allows translation and conversion so that Ruby can be used to perform more complex tasks within Rails.
When using a database, there needs to be checks that the data being added, removed or altered is in order, and is not a threat to the security or functionality of the database. Active Record includes validation features so data offered to the database can be validated before being accepted and persisted. In this blog we will cover an overview of Active Record validations and error handling.
Let's say you are setting up a Rails application with a User class with the following attributes: username, age and address.
Imagine a scenario where a form is submitted from the client side. What would happen if the user didn't include a name or submitted their age as a negative number or included fields that should't be edited/updated by the user. Obviously, this is invalid data, and we would want to include validations that prevent them from being saved to the database.
The client side of an application can validate form data, but there needs to be an insurance on the server side, which, ideally, is inaccessible. This is where Active Record validations come in. Validations can be used in different ways, but a great place to start is with the helpful validation helpers that Active Record provides. These helpers are established to provide common use validations like whether a required field was entered, or an entry matches the required length of characters. Adding some validations for our use attributes could look something like this.
class User < ApplicationRecord
validates :name, presence: true
validates :age, numericality: {
greater_than_or_equal_to: 1
}
validates :address, length: { minimum: 6 }
end
Here we are validating that a POST request includes a name, age is number greater than or equal to 1 and that address has a minimum character count of six. if we try to create a new user and any of these validations fail, then we will get an error. These are just simple examples and there are many other validation options that can be utilized when building your backend. There is also the ability to create custom validations which you can reference here.
ERROR HANDLING
Now that we've seen how Active Record can be used to validate our data, let's see how we can use that in our controllers to give our user access to the validation errors, so they can fix their typos or other problems with their request.
We can use a tool like postman to help with testing our API. I have set up a simple create rout for creating a new user.
class UsersController < ApplicationController
def create
user = User.create(user_params)
render json: user, status: :created
end
private
def user_params
params.permit(:name, :age, :address)
end
end
Here is what would happen if we try and submit a POST request without including the user age.
Request:
{
"name": "Blizz",
"address": "12345 Street"
}
Response:
{
"name": "Blizz",
"age": null,
"address": "12345 Street"
}
The server still returns a user object, but it is obvious that it is not saved successfully. The model validation prevented the bad database, but we did not receive any information regarding why the information was not saved(error). In order to provide context, we can update the user controller to change the response if the user was not save successfully.
There are three different ways we can go about using the controller to return errors:
If/else statements
Rescue
Rescue_from
If/Else
Using an if/else will look something like this:
class UserController < ApplicationController
def create
user = User.create(user_params)
if user.valid?
render json: user, status: :created
else
render json: {errors: user.errors}, status: :unprocessable_entity
end
end
This is a perfectly acceptable way to handle validation errors. But abiding to DRY code rules, writing all this code for every single error would be very time consuming, especially when the status codes you're targeting are repetitive and generic enough messages. Here is the error that is returned when sending the same POST request from earlier.
{
"errors": {
"age": [
"is not a number"
]
}
}
Reviewing the server will also show that the 422 Unprocessable Entity status code was returned:
Completed 422 Unprocessable Entity in 8ms (Views: 6.5ms | ActiveRecord: 0.2ms | Allocations: 3821
Rescue
A more efficient way to return errors is to use rescue which looks like this:
class UserController < ApplicationController
def create
user = User.create!(user_params)
render json: user, status: :created
rescue ActiveRecord::RecordInvalid => invalid
render json: {errors: invalid.record.errors.full_messages), status: :unprocessable_entity
end
private
def user_params
params.permit(:name, :age, :address)
end
end
There are a few now concepts to be aware of to ensure rescue functions properly:
The addition of the rescue section tells the exception operator what to execute if it runs into errors. It's important to anticipate what Active Record errors you will receive so you can properly diagnose the message and status. In this example you can tell the error is an unprocessable_entity error which ahs a 422 status code. You can reference rails status codes here.
In the rescue block, the invalid variable is an instance of the exception itself. From that invalid variable, we can access the actual Active Record instance with the record method, where we can retrieve the errors.
The addition of ! at the end of the create call. This is the exception operator. By adding the exception operator, the instance method will know that if the methed produces a nill result, it will skip to find the rescue associated with its error, which is the ActiveRecord:RecordInvalid in this case.
Rescue_from
We can still improve our code by making it more DRY using rescue_from. The below example shows how you can handle all ActiveRecord::RecordInvalid exceptions using the rescue_from method.
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
def create
user = User.create!(user_params)
render json: user, status: :created
end
private
def user_params
params.permit(:name, :age, :address)
end
def render_unprocessable_entity_response(invalid)
render json: {errors: invalid.record.errors}, status: :unprocessable_entity
end
end
As you can see rescue_from is the most scalable way to handle errors of the three options. The best part is you can write rescue_from in your application controller and all other controllers will inherit the rescue_from. Note that you will need to make a new rescue_from to handle each different type of error.
Posted on May 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.