[Ruby] Dry up your data validation

Build reusable validations with dry-validation

Ravic Poon
3 min readJul 18, 2022
source: Pinterest

Why flavour dry-validation over the “Rails way”?

In terms of data validation, the Rails framework offers one of the best solutions such as ActiveRecord and ActiveModel validations. They work great out of the box and create validation rules for ActiveRecord models with low effort.

Although ActiveModel validator can build custom validator object that inherits from ActiveModel::Validator, it still tightly couples with the domain models. As your application becomes more complex, the use of validators tends to become harder to break out and reuse parts of the logic to validate data in a different context.

On the other hand, dry-validation offers the ability to create validation schema (“Contract”) that is model-independent, has similar implementation efforts to ActiveRecord, and does not couple with models. These characteristics make it a promising alternative if you want to extract parts of your validation logic from a domain model and reuse them in other parts of your application.

“Value single responsibility” is the philosophy behind this gem that makes it so easy to work with any Ruby application at different scales; even the Hanami framework is building its validation gem based on it.

When to adopt this gem?

One of the reasons to leverage dry-validation is that you can validate any kind of data at the earliest entry point to your services, such as HTTP request parameters and entities from other domain services.

Imagine you are building an inventory management system that has different types of user accounts: manager and clerk.

Only managers have administrative access to the inventory. They can edit, product information such as names, stock amounts, and prices. So you are implementing this POST /api/product API:

curl --location --request POST 'https://inventorysystem.com/api/product' --header 'Authorization: Bearer This1sN0TthEDr0idY0uArEL0oK1Ng4' --header 'Content-Type: application/json' --data-raw '{"product": {"name": "Smarties","amount": 100,"price": 1.99}}'

Without dry-validation, it makes sense to implement validation logic to different models. For instance, the User model should validate the account type and the Product model should validate its attribute constraints. You will likely end up with these kinds of implementations:

Perhaps it looks straightforward to read for now, but as your domain grows and starts to interact with more objects from other domains, more constraints have to be implemented and tested.

Eventually, you will end up with a bloated controller method or a fat model with a lot of validation rules that only make sense under the same context. Let alone writing tests for the validation blocks individually.

How to use the gem?

Since dry-validation supports Dependency Injection (“DI”), it allows us to pass the dependency (“User”) into other objects (“CreateProductContract”) instead of instantiating them within the object. This enables us to write unit tests a lot easier by swapping/stubbing out the dependency.

By leveraging the gem, we can clean up the validation logic by aggregating the user and product validations into the same validator:

What else can you benefit from using dry-validation?

In terms of localization, you can create a YAML file and declare the message strings and append it to the configuration. Here is a sample class to demonstrate how other contract objects can inherit from it.

You can also declare flexible validation rules that are not bounded by attributes in the contract:

Or apply advance, case-specific validation rules to attributes in the contract:

Wrap-up

Is dry-validation the silver bullet to every validation throughout your rails application? The short answer is: No.

Although it allows you to write loosely coupled validators (“Contracts”), that doesn’t mean you should go ham and break out chunks of existing ActiveRecord validates in your app. In terms of performing CRUD with the Rails framework, ActiveRecord/ActiveModel validations remain the best way to make sure only valid data is inserted into the database.

I would recommend you give this gem a try if you want to apply validation to objects that are not coupled with ActiveRecord/ActiveModel. For instance: HTTP request param and value objects from other domain services.

There are so many features in this gem that I haven’t covered yet, such as custom Error Messages, Rules, Nested Schemas, and much more.

I will dive deeper into the dry-rb frameworks and showcase how to utilize them practically in your Ruby applications; stay tuned for more!

--

--