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])