paint-brush
Performing custom validations in Rails — an exampleby@rfleury2
57,450 reads
57,450 reads

Performing custom validations in Rails — an example

by Ricardo FleuryJanuary 27th, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

<a href="https://hackernoon.com/tagged/rails" target="_blank">Rails</a> provides a variety of helpers out of the box for quickly <a href="https://hackernoon.com/tagged/performing" target="_blank">performing</a> commonly used validations — presence, numericality, uniqueness, etc. If the model has validations that go beyond the standard helpers, we must implement a custom validation strategy. I will show three ways of going about validations and discuss pros and cons.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Performing custom validations in Rails — an example
Ricardo Fleury HackerNoon profile picture

Rails provides a variety of helpers out of the box for quickly performing commonly used validations — presence, numericality, uniqueness, etc. If the model has validations that go beyond the standard helpers, we must implement a custom validation strategy. I will show three ways of going about validations and discuss pros and cons.

For example, we have a Shipments table where each record represents a package with attributes width, height, depth, and weight.

Each shipment must adhere to the following rules:

  • The volume of the shipment must be between 20 and 4000 cubic centimeters (ie. volume validation)
  • The density of the shipment cannot exceed 200 grams per cubic centimeter (ie. density validation)
  • No side length can be less than 10% of the largest side (ie. proportion validation)

For each of these cases, we will use a different validation implementation. In order to make the validations more readable, we implemented the #volume and #density in the Shipment model (check out the source code).

First, let’s validate the shipment volume by creating a custom method in the Shipment class. We can use #validateto call a custom method during the validation. Then, in the custom method, add new errors to the #errorsobject (which deserved its own short post)




class Shipment < ActiveRecord::Base…validate :volume_limits…

private








def volume_limitsif volume > 4000errors.add(:volume, “cannot be above 400 cubic inches”)elsif volume < 20errors.add(:volume, “cannot be below 20 cubic inches”)endendend

Performing validations within the model works fine, but it also adds more logic to the model. I prefer to extract that logic to its own helper class when possible. That’s because it nicely encapsulates each validation’s logic to its own object, making it easier to debug and/or extend in the future.

Let’s validate the density by creating a helper validator class. The #validates_withmethod points the validation at a helper class:





class Shipment < ActiveRecord::Base…validates_with DensityValidator…end

Then, in the /models/concerns directory, create “density_validator.rb”. The DensityValidator inherits from ActiveModel::Validator, whose convention is there to be a method called #validate. The method has access to the entire record and implements the validation logic, assigning errors if needed:







class DensityValidator < ActiveModel::Validatordef validate(record)if record.density > 20record.errors.add(:density, “is too high to safely ship”)endendend

The last validation is to ensure that packages are not oddly shaped. A package is said to be oddly shaped if any side’s length is shorter than 10% of the longest side.

For both of the examples above, we were validating properties of the shipment as a whole — there is a single volume and a single density for any package. In case of the package’s shape, each side must be validated separately.

Using #validates, we list all of the attributes to undergo validation, followed by package_proportion: true:





class Shipment < ActiveRecord::Base...validates :height, :width, :depth, package_proportion: true...end

The package_proportion flag means that we expect there to be a validation helper class named PackageProportionValidator. In the /models/concerns directory, create “package_proportion_validator.rb”.








class PackageProportionValidator < ActiveModel::EachValidatordef validate_each(record, attribute, value)if value < [record.width, record.height, record.depth].max * 0.1record.errors.add(attribute, "cannot be so short as to makethe package oddly sized :(")endendend

The attribute validation helper class expects a #validates_each method to facilitate the validation. It provides access to the individual attribute currently being validated and the entire record as separate variables, which is helpful.

Let’s trip all of these validations with an oddly shaped, too big and too heavy package.

These are three different ways of implementing validations in Rails. Which one is best will depend on the application goals and the validation use case. I usually try to encapsulate the logic to a separate class to keep the validation logic away from the model. When the validation happens at the attribute level (proportion example), I use the validates_each strategy to have access to the attribute separately. When the validation happens at the object level, I use a custom validator for the entire object (density example).

Thank you for reading. I’m new at writing about code, so I’d appreciate any feedback that helps be a better writer and communicator. Also open to future topics!