backstage blog

Build your own Rails custom validators

, August 27th, 2012 | Rails, Ruby

Overview

Validating your data through Rails models is essential. It enables you to ensure consistency throughout your data structures. You may also want to validate data on the client and/or database side. But even still, keeping your validation in your Rails models is a good practice because you can create real meaningful states for your Ruby objects that are independent of other levels.

Custom validators

Rails provides built in validators like Presence, Confirmation, Numericality, Acceptance, etc.. which you should already be familiar with.

But you can also build custom validators if the existing ones don’t satisfy your requirements. I’ll guide you through three different ways to create custom validators for your models, but before we get started, let me introduce you to the module defining these validators: ActiveModel.

What is ActiveModel?

ActiveModel gathers all related model behaviors except its database layer (kept in ActiveRecord). By including ActiveModel, you give a class all of Rails model super powers such as translations, errors, callbacks, dirty states, observer support, serialization… and also validations!

Validate object states directly in your model with validates_each

The simplest way to create a custom validator is to use validates_each.

This method validates each attribute against a block.

Suppose you want to create a representation of an existing music album. Since such an  album has already been released, you don’t want your album data structure to accept a future context date as a released date.

You could validate the released date by defining a Rails 2.x style class method validates_against_future which will use validates_each to apply validation to all passed attributes, like in this example:

Validate object single states with an isolated custom validator

When you only want to validate single states of an object and you know you will reuse this validation elsewhere, extracting it into its own class is the most modular approach. You will also be able to use it as an ordinary validates option in your models.

In order to achieve this, you must inherit from ActiveModel::EachValidator and implement the validate_each method (be careful to use “validate_each” and not “validates_each” this time).

Based on the previous example, let’s re-write the validates_against_future validation for our existing album representation. We will call this validator PreventFutureDateValidator.

Note: the prevent_future_date: true option passed to validates will cause ActiveModel to search for a class named 'prevent_future_date'.camelcase + Validator (i.e. PreventFutureDateValidator) and call its validate_each method on the attribute.

Validate entire object with an isolated custom validator

A custom validator also allows you to validate an entire record instead of just a record’s attributes. To do this, you will have to create a separate class that inherits from ActiveModel::Validator and implements the validate method. To apply the validation, simply call validates_with in your model and pass the custom validator as the argument.

Valid8ors

Here at Official.fm, we use custom validators for the benefits listed above. We usually prefer the second approach for creating them as we want to keep validations as record independent as possible.

But we took the modularity a step further by making some of our custom validations “project free” by packaging and releasing them as a gem.

This gem is called valid8ors and you can check it out on GitHub or just install it via a simple gem install valid8ors command.

Conclusion

Creating custom validators with Rails is pretty straightforward once you know how they work. By providing different ways to build them depending on your needs, custom validators make your code DRYer and more modular, and thus more maintainable and readable.

So take some time and look back at your code and ask yourself if you can extract some of your custom validations from your models.

Tags: ,

Posted by Axel Vergult

Back-end Engineer