Ruby on Rails

4 levels of the data validation in a Rails application you should be aware of

4 levels of the data validation in a Rails application you should be aware of April 6, 2018

If you receive a user input in your application, you need a validation – no doubt about it. When thinking about the validation in a Ruby on Rails application we used to think about a model validation in the first place. But how about other levels of the validation? Is the model validation a bulletproof solution? I will go quickly through main four levels of the validation in the Rails application and I will discuss advantages and disadvantages of each solution using the email column in the User model as an example.

Model level validation

This is the common approach in the Rails application. We want to ensure that email will be always present for our User records so we can define the following validation:

class User < ActiveRecord::Base
  validates :email, presence: true
end

This way of protecting our data is proper but keep in mind that it is still possible to create the User record without the email. While calling User#save or User#save! will not save the invalid record in the database, these methods will:

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

We should be careful when using them, especially using methods with the update_ prefix. What about the case where sometimes we don’t need to validate the email column? Then we can consider passing :if option or using the controller level validation.

Controller level validation

As mentioned above, sometimes we have to validate the given column only in some cases. While passing :if or :unless option is still a good choice it may complicate our validation rules and make our validations less readable and less testable. Controller level validation is an alternative solution. To do it right, I will suggest to use the Form Object pattern. However, using the controller level validations are much more difficult to maintain than the model level validations. I find the Form Object pattern very useful in a very large application where one model has many validations that are sometimes optional and sometimes required.

Database level validation

This type of validations is the safest which means that there is no way to save a record with the invalid value when you put the validation on the database level. The most common validations are presence and uniqueness - both validations are perfect for the email column in the User model. How to add them? By creating a migration:

class AddValidationOnUserEmail < ActiveRecord::Migration
  def change
    change_column :users, :email, :string, null: false
    add_index :users, :email, unique: true
  end
end

After running rake db:migrate we can test our validations. Let’s use update_column method which will not call model validations. Try to save the user without the email:

user = User.find(user_id)
user.update_column(:email, nil) # => raises ActiveRecord::StatementInvalid: Mysql2::Error: Column 'email' cannot be null

it will raise an error. Now try to save the user with the duplicated email:

user = User.find(user_id)
user_2 = User.find(user_2_id)

user.update_column(:email, user_2.email) # => raises ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry

Both validations are different. While presence validation we can pass in the column definition, for the uniqueness validation we have to create a proper index in the database. The error will be also raised when you will try to save an invalid type of data:

user = User.find(user_id)
user.update_column(:created_at, "string") # => raises ActiveRecord::StatementInvalid: Mysql2::Error: Incorrect datetime value

As you can see, it is important to carefully define the columns and create proper indexes not only because of performance reasons but also security reasons.

Front-end validation

This is the least safe type of the validation. You can bypass it by disabling JavaScript support in a browser or performing the request directly using code or a browser extension like the Postman.

You should always protect your data with the back-end validation first. However, having the front-end validation is the best way to improve user experience - I love that feeling when I don’t have to wait for the form to be submitted and application marks errors in my form on the fly.

Hungry for more Rails and Ruby articles? Follow me on twitter and don't miss any new article or just say hello!

Download free RSpec & TDD ebook

Do you want to earn more or jump to the next level in your company? Do you know that testing skills are one of the most desired skills? There is only first step: start testing and do it right. My ebook can help you. Subscribe to the newsletter to get a free copy of the book.