(String: {%- set hs_blog_post_body -%} {%- set in_blog_post_body = true -%} <span id="hs_cos_wrapper_post_body" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_rich_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="rich_text"> <div class="blog-post__lead h2"> <p>Every program receives some kind of input data<g class="gr_ gr_126 gr-alert gr_gramm gr_inline_cards gr_disable_anim_appear Style replaceWithoutSep" id="126" data-gr-id="126"> .&nbsp; </g><g class="gr_ gr_126 gr-alert gr_gramm gr_inline_cards gr_disable_anim_appear Style replaceWithoutSep" id="126" data-gr-id="126" style="background-color: transparent;"> It </g><span style="background-color: transparent;"> can by anything - from variables, command line options, HTML web forms, and&nbsp;</span><span style="background-color: transparent;">configuration files to binary data.&nbsp;</span></p> </div></span>)

Dry-validation Basics

Photo of Mateusz Kluge

Mateusz Kluge

Updated Feb 13, 2023 • 7 min read
todd-quackenbush-701-unsplash

Every program receives some kind of input data . It can by anything - from variables, command line options, HTML web forms, and configuration files to binary data.

All of this needs to be checked to prevent unexpected errors from happening. Although ActiveModel::Validations is great for web apps and simple models, it isn't very flexible for other types of validation and complex dependencies.

Luckily, we have an alternative called dry-validation. It is one of the tools provided by the dry-rb collection. You can find more at http://dry-rb.org/, there are many great libraries. But let's focus on dry-validation for a moment. It was designed from the start to act as general purpose validation framework capable of handling complex logic. It is also very performant in comparison to other libraries.

In this post I will give a brief overview to the basic rules of dry-validation.


Basics

Installation is pretty simple, you just need to add the dry-validation gem to your Gemfile and require it. We are going to validate this simple data structure.

{
    name: 'John',
    surname: 'Doe'
}

The first thing we need to do is define the validation schema.

require 'dry-validation'
    
validator = Dry::Validation.Schema do
    # validation rules
end

As you can see it takes a block as a parameter. We will add validation rules inside of it. You can think of schemas as classes that contain validation rules. A schema with no rules will always pass for any input. You can check it yourself by using UserValidator like a proc object (it responds to call).

validator.call('some input').success? #=> true
validator.call({}).success? #=> true
validator.call([]).success? #=> true

If you are familiar with dry-monads (yet another library from dry-rb), you should notice that we are accessing a Result object. For those of you who don't know, the Result object can have two forms: success or failure. We can check which one was returned by calling the success? method. It'll return a boolean. It can also carry some data, but that's another topic.


Let's get back to our schema and add some rules. To ensure that a specific key is present, we use the required method. Dry-validation makes a clear distinction between key presence and empty values. This concept is skipped in ActiveModel::Validations

where presence validation fails both when the key is not present and when the value is empty ('', [], {}, nil). Let's say we want to ensure that name and surname are non-empty strings. We can write it as follows.

validator = Dry::Validation.Schema do
    required(:name) { filled? && str? }
    required(:surname) { filled? && str? }
end

required takes a block with validation logic composed with simple predicates.

You can check out the built-in predicates here https://dry-rb.org/gems/dry-validation/basics/built-in-predicates/

validator.call({
    name: 'John',
    surname: 123
}).success? #=> false


validator.call({
    name: 'John',
    surname: 'Doe'
}).success? #=> true

As you can see, we can use logical operators. You are probably wondering why we've used filled? and str? and not just str? alone. str? ensures that the value is of the string type, and filled? checks if the value is not empty. For strings, it must be at least 1 character long, so '' is considered an empty value.

Nested rules

Let's move on to a slightly more advanced topic, which is validating nested data.

Consider the following structure

{
    name: 'John',
    surname: 'Doe',
    contact: {
        phone: '+48 123456789',
    }
}

As you can see, I've added a nested hash for contact information. Let's say that a phone is required and it must be properly formatted. The email field can be omitted, but if it's present it should also have a valid format.

# these are the simplified regex, don't use them in production!

PHONE_REGEX = /\A\+?[\ \d]*\z/
EMAIL_REGEX = /\A[\w \.]+@[\w \.]+\z/

validator = Dry::Validation.Schema do
    required(:name) { filled? && str? }
    required(:surname) { filled? && str? }
    required(:contact).schema do
        required(:phone) { filled? && format?(PHONE_REGEX) }
        optional(:email) { filled? && format?(EMAIL_REGEX) }
    end
end

Conclusion

Dry-validation is a really flexible and powerful validation library. It's getting more and more popular in the Ruby community. There are many features I haven't covered like error handling, arrays, reusable schemas and more. To learn more, check the official documentation page.

Photo of Mateusz Kluge

More posts by this author

Mateusz Kluge

Mateusz is a self-taught programmer who is not afraid of solving difficult problems. Over about 6...
How to build products fast?  We've just answered the question in our Digital Acceleration Editorial  Sign up to get access

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by: