DEV Community

jkap100
jkap100

Posted on • Edited on

Rails - Validations & Error Handling

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Here is what would happen if we try and submit a POST request without including the user age.

Request:

{
"name": "Blizz",
"address": "12345 Street"
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
"name": "Blizz",
"age": null,
"address": "12345 Street"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
      ]
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)