Validated
Suppose you've got a form to validate. If you are using Result
combined with Do
your code might look like this:
require 'dry/monads'
class CreateAccount
include Dry::Monads[:result, :do]
def call(form)
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
def validate_name(form)
# Success(name) or Failure(:invalid_name)
end
def validate_email(form)
# Success(email) or Failure(:invalid_email)
end
def validate_password(form)
# 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
:
require 'dry/monads'
class CreateAccount
include Dry::Monads[:list, :result, :validated, :do]
def call(form)
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
def validate_name(form)
# Valid(name) or Invalid(:invalid_name)
end
def validate_email(form)
# Valid(email) or Invalid(:invalid_email)
end
def validate_password(form)
# 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])