Understanding Rails Custom Validations

Validations is one of the core features that rails provides. But the bigger question here is Why should we use Validations?

Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid mobile number and format of an email should be correct, presence of name should be there, for a note minimum length must be taken care of. Model-level validations are the best way to ensure that only valid data is saved into your database. They cannot be bypassed by the end users, and are very easy to test and maintain.

There are several other ways to validate the data before saving into the DB, including native DB constraints, client-side validations, and controller-level validations.

  • Database constraints make the validation mechanisms database-dependent and can make testing and maintenance difficult. However, if your database is used by other applications too, it may be a nice idea to use some constraints at the database level. Adding to that, database-level validations can safely handle some things (eg: uniqueness in heavily-used tables) that can be difficult to implement otherwise.
  • Client-side validations can be useful, but are generally capricious if used alone. If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user’s browser.
  • Controller-level validations can be tempting to use, but often become unwieldy and difficult to test and maintain. Controllers should be leaner, as its easy to maintain for a long run.

Choose these in certain, specific cases. It’s the advisable to use rails model-level validations as they are the most appropriate in most circumstances.

There are a lot of built in validation helpers in rails, which helps validating our form inputs or user attributes. Some of the examples are , presence, length, uniqueness, format, etc. But in few cases, if these built in validation helpers doesn’t help, rails provide support for writing our own custom validators as well.

Ways to write custom validations are:

  • Use a custom method to perform the validation.
  • Creating a helper validator class
  • Create an EachValidator class which validates a particular attribute. The one, for which it has been called.

Let us consider a requirement and then understand the need of each, or any one of these:

Let’s consider we have an Item model, with table name as items , where each record represents a package with attributes as title, description ,width, height, depth, and weight.

Each item must obey the following rules:

  • We want title should be of minimum length of 10 chars, and each title should contain a keyword i.e. great-article .
  • The volume of the item must be between 20 and 3000 cubic meters (ie. Volume Validation)
  • The compactness of the item cannot exceed 200 grams per cubic meters (ie. Compactness Validation)
  • No side length can be less than 15% of the largest side (ie. proportion validation).

Now let’s pick up the first rule, with first approach

  • Use a custom method to perform the validation.
class Item < ActiveRecord::Base  ...
validate :length_and_string_presence
validates :title, presence:true, length: { minimum: 10 }
...
private

def title_rules_adhered?
title.include?("great-article")
end
def length_and_string_presence
unless title_rules_adhered?
errors.add(:title, "must contain 'great-article'")
end
end
end

We have used the very first approach for custom validation, i.e. define a validate method inside the model class.

It works fine, but it also adds more logic to the model. Its preferable to extract the logic into a separate helper class whenever possible. That’s because, all the logic for a particular validation is encapsulated to its own object, making it easy to debug.

Let’s validate the density by using the second approach i.e. by creating a helper validator class. The #validates_withmethod provided by rails points the validation to a helper class:

class Item < ActiveRecord::Base

validates_with CompactnessValidator

end

Now we need to create a helper class CompactnessValidator

In the /models/concerns directory, create “compactness_validator.rb”. The CompactnessValidator inherits from ActiveModel::Validator, whose propriety is there to be a method called #validate. This method can access entire record. And can apply errors to the record if needed.

class CompactnessValidator << ActiveModel::Validator
def validate(record)
if record.compactness > 10
record.errors.add(:compactness, “is too high to safely dispatch”)
end
end
end

Note, here we can add n-number of validations in this same class for various attributes. eg. we can even add validation for title in this class.

for eg.

class CompactnessValidator << ActiveModel::Validator
def validate(record)
if record.compactness > 10
record.errors.add(:compactness, “is too high to safely dispatch”)
end
if record.title.length < 10
record.errors.add(:title, "length must be greater than 10")
elsif !record.title.include?("great-article")
record.errors.add(:title, "must include great-article")
end
end
end

Now lets use the third approach of defining custom validations.

By creating a custom validator helper, we can directly use it just like rails inbuilt ones, just by saying, “item_dimensions_proportion:true”.

class Item < ActiveRecord::Base
...
validates :height, :width, :depth, item_dimensions_proportion: true
...
end

Now as we have set item_dimensions_proportion flag to true, rails will now expect to have a helper class for validation named ItemDimensionsProportionValidator. So in the models concern directory create a file “item_dimensions_proportion_validator.rb”.

class ItemDimensionsProportionValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value < [record.width,record.height,record.depth].max * 0.15
record.errors.add(attribute, "cannot be so short as to make
the package oddly sized :(")
end
end
end

We have to define a #validates_each method to facilitate the validation. It will take each attribute, and will check the value according to the logic written in the validate_each method.

Using custom validators is a great, if you want to extract the validation related to code to a separate class. It’s really helpful to debug too as all the validation code for a model lies at one place. Also we can even use the same validators for multiple classes, which makes it really handy and awesome.

Try out these custom validators in your apps, and let me know if this has helped you in a positive way. Also you can reach out to me in case of any issue @ abhinav.garg1218@gmail.com.

I would also like to increase my readership, so would kindly request you to share it with you friends, peers, etc, if you find this blog helpful.

Thanks.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store