Rails application data validation levels

published on NOV 1, 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

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

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.

Tagged: Ruby Ruby on Rails

Cookies help us deliver our services. By using our services, you agree to our use of cookies.