Suppose you've got a form to validate. If you are using Result combined with Do your code might look like this:
include Dry::Monads[:result, :do]
name = yield validate_name(form)
email = yield validate_email(form)
password = yield validate_password(form)
user = repo.create_user(
name: name,
email: email,
password: password
)
Success(user)
end
# Success(name) or Failure(:invalid_name)
end
# Success(email) or Failure(:invalid_email)
end
# Success(password) or Failure(:invalid_password)
end
end
If any of the validation steps fails the user will see an error. The problem is if name is not valid the user won't see errors about invalid email and password, if any. Validated circumvents this particular problem.
Validated is actually not a monad but an applicative functor. This means you can't call bind on it. Instead, it can accumulate values in combination with List:
include Dry::Monads[:list, :result, :validated, :do]
name, email, password = yield List::Validated[
validate_name(form),
validate_email(form),
validate_password(form)
].traverse.to_result
user = repo.create_user(
name: name,
email: email,
password: password
)
Success(user)
end
# Valid(name) or Invalid(:invalid_name)
end
# Valid(email) or Invalid(:invalid_email)
end
# Valid(password) or Invalid(:invalid_password)
end
end
Here all validations will be processed at once, if any of them fails the result will be converted to a Failure wrapping the List of errors:
create_account.(form)
# => Failure(List[:invalid_name, :invalid_email])