For long time Hanami::Validations had problems that we struggled to solve and new features were problematic to add. Data management is complex task with thousands of cases to cover and because validations deal untrusted input, edge cases are common. Even simple cases like blank values management became an issue.

We tried to fix these problems, but over the time we realized that we hit the limit of that syntax, which led to lack of flexibility for us and for developers themselves.

At the same time dry-rb folks released a new, stronger validations gem: dry-validation. It changes, for the good, the way we express validation rules. So we took the decision to radically change our syntax and to adopt dry-validation as a validations backend for us.

How It Will Work?

Hanami::Validations will work with input hashes and let define a set of validation rules for each key/value pair. These rules are wrapped by lambdas (or special DSL) that check the input for a specific key to determine if it's valid or not. To do that, we translate business requirements into predicates that are chained together with Ruby faux boolean logic operators (eg. & or |).

Think of a signup form. We need to ensure data integrity for the name field with the following rules. It is required, it has to be: filled and a string and its size must be greater than 3 chars, but lesser than 64. Hereโ€™s the code, read it aloud and notice how it perfectly expresses our needs for name.

class Signup
  include Hanami::Validations

  validations do
    required(:name) { filled? & str? & size?(3..64) }
  end
end

result = Signup.new(name: "Luca").validate
result.success? # => true

result = Signup.new({}).validate

result.success? # => false
result.messages.fetch(:name) # => ["must be filled"]

Boolean Logic

When we check data, we expect only two outcomes: an input can be valid or not. No grey areas, nor fuzzy results. Itโ€™s white or black, 1 or 0, true or false and boolean logic is the perfect tool to express these two states. Indeed, a Ruby boolean expression can only return true or false.

To better recognise the pattern, letโ€™s get back to the example above. This time we will map the natural language rules with programming language rules.

        A name must be filled  and be a string and its size fall between 3 and 64.
           ๐Ÿ‘‡            ๐Ÿ‘‡     ๐Ÿ‘‡        ๐Ÿ‘‡    ๐Ÿ‘‡       ๐Ÿ‘‡                ๐Ÿ‘‡    ๐Ÿ‘‡
required(:name)      { filled?  &       str?   &      size?              (3 .. 64) }

Now, I hope youโ€™ll never format code like that, but in this case, that formatting serves well our purpose to show how Rubyโ€™s simplicity helps to define complex rules with no effort.

From a high level perspective, we can tell that input data for name is valid only if all the requirements are satisfied. Thatโ€™s because we used &.

But there is more. Rule composition with blocks is powerful, but it can become verbose. To reduce verbosity, Hanami offers convenient macros that are internally expanded (aka interpreted) to an equivalent block expression.

required(:name).filled(:str?, size?: 3..64)

The Advantages

With this new syntax we give more control to developers: they can decide the order of execution of the validations. They can define custom predicates and custom error messages, opt in for internationalization (i18n) with small effort. They can dry code via macros, reuse validators, enforce types, whitelist params.

To summarize: we fixed old bugs, implemented features that developers asked for, increased internal code robustness and started a new alliance with dry-rb <3.

These chances were just merged in master and they will be released in a few months with hanami v0.8.0.