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
User model as an example.
Model level validation
This is the common approach in the Rails application. We want to ensure that
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! will not save the invalid record in the database, these methods will:
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
: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
: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
uniqueness - both validations are perfect for 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
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.
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.