All For One, And One For All: A Single Ruby on Rails Validator For All Controllers
Recently, I’ve handled a lot of support cases that were caused by us not validating user input. Those bugs created a bunch of tickets and caused me a major headache.
It’s simply because users DON’T read documentation, and even when they do, frequently they misunderstand or misuse it. Our job as developers, especially when writing an API for others to use, is to make sure that we handle input errors correctly and return meaningful responses accordingly.
That is one of the most important things we have to pay attention to when writing a REST API: validations. A good set of validations would spare you the need of investigating a hidden bug that causes ambiguous error messages. Or even worse, sends malformed data to a third party, resulting in an exception or an error you can’t address.
There are many gems out there that can help you handle input validations, but my team chose to work with Rails’ Active Model Validations.
Simply by including the ActiveModel::Validation to your model, you can use rails’ models-level validations, for a model which is not necessarily an active record. I’ve found it convenient, since we could declare a small model, just for the sake of validating its data.
In the example above, we validate two attributes: submission_type and email and make sure they exist.The controller is capable of accepting more attributes, we chose to validate only two.
Another awesome feature is the ability to define our own validators, for custom validations, which are not defined by default in rails (such as a special format of a date, or validating a legitimate format of ID).
First, you need to create your validator under the app/validator directory. Then, your validator class needs to inherit from ActiveModel::Validator, add errors to the record, and let the active model validation do its magic.
Email Validator - checks if the email is in a valid R22 format:
String Length Validator — checks if a string is shorter than the permitted length:
Now let’s add these validations into the SubmissionParams from our initial example, making sure the length of email doesn’t exceed 80 characters, while phone length is less than 40 characters:
Though it was cool and easy enough, it did require us to add the same validations over and over again. Yeah, of course we did create custom validations for each known type such as email, date, time, etc., but we still needed to implement the validation for each and every attribute.
Let’s assume you need another controller to validate email and phone attributes.You would probably need to re-write the same code again. And as we know, code duplication is bad.
Another possible scenario is a different controller that only receives an email, without a phone. We would like to validate it , without duplicating the code again. So what’s next?
What I really wanted to find is a generic super validator. One that you only need to implement once, and by including it to the validation-needed class, you would get all validations for free. Sounds good, right?
I’ve searched the net quite a bit, looking for the perfect gem that will give me what I wanted, but found nothing. That is when I decided to implement it by myself.
The idea was simple; the only thing I had to overcome was finding out which attribute was defined for a specific class, which actually turned out to be kind of a tough problem.
Luckily, ActiveSupport::Concern helped me solve this issue.
I created a module named BaseValidations and implemented the common validations within this module, while making sure the attribute exists in the parent class before performing the validation.
That’s it. By including the module into our class, we will only need to declare the attributes without having to re-write the validation part.
Finally, let’s see how it is used in the controller:
Don’t forget to add all the common validations to your BaseValidations so you can avoid code duplications while validating all your controller data!
More details can be found in Rails’ official documentation:
Subscribe to get your daily round-up of top tech stories!