Performing custom validations in Rails — an example
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)
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:
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.
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!