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.

Want to receive useful tips, information about new Ruby gems and articles on a daily basis? Make sure you follow me and say hello!

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.

Want to become a better Rails developer?
Download for free the Introduction Rails patterns book and dive into the world of refactoring and easy-testable Ruby code today.
Join over 1,000 developers already subscribed to my newsletter and download the book. You can unsubscribe anytime:

Subscribe and get the book!