Working With Schemas
A schema is an object which contains a list of rules that will be applied to its input when you call a schema. It returns a result object
which provides an API to retrieve error messages
and access to the validation output.
Schema definition best practices:
- Be specific about the exact shape of the data, define all the keys that you expect to be present
- Specify optional keys too, even if you don't need additional rules to be applied to their values
- Specify type expectations for all the values!
- Use custom predicates to keep things concise when built-in predicates create too much noise
- Assign schema objects to constants for convenient access
- Define a base schema for your application with common configuration
Calling a Schema
Calling a schema will apply all its rules to the input. High-level rules defined with the rule
API are applied in a second step and they are guarded, which means if the values they depend on are not valid, nothing will crash and a high-level rule will not be applied.
Example:
schema = Dry::Validation.Schema do
required(:email).filled
required(:age).filled
end
result = schema.call(email: 'jane@doe.org', age: 21)
# access validation output data
result.to_h
# => {:email=>'jane@doe.org', :age=>21}
# check if all rules passed
result.success?
# => true
# check if any of the rules failed
result.failure?
# => false
Defining Base Schema Class
class AppSchema < Dry::Validation::Schema
configure do |config|
config.messages_file = '/my/app/config/locales/en.yml'
config.messages = :i18n
end
def email?(value)
true
end
define! do
# define common rules, if any
end
end
# now you can build other schemas on top of the base one:
Dry::Validation.Schema(AppSchema) do
# define your rules
end
Working With Error Messages
The result object returned by Schema#call
provides an API to convert error objects to human-friendly messages.
result = schema.call(email: nil, age: 21)
# get default errors
result.errors
# => {:email=>['must be filled']}
# get full errors
result.errors(full: true)
# => {:email=>['email must be filled']}
# get errors in another language
result.errors(locale: :pl)
# => {:email=>['musi być wypełniony']}
Using Validation Hints
In addition to error messages you can also access hints, which are generated from your rules. While errors
tells you which predicate checks failed, hints
tells you which additional predicate checks weren't evaluated at all because an earlier predicate failed:
schema = Dry::Validation.Schema do
required(:email).filled
required(:age).filled(gt?: 18)
end
result = schema.call(email: 'jane@doe.org', age: '')
result.hints
# {:age=>['must be greater than 18']}
result = schema.call(email: 'jane@doe.org', age: '')
result.errors
# {:age=>['must be filled']}
result.hints
# {:age=>['must be greater than 18']}
# hints takes the same options as errors:
result.hints(full: true)
# {:age=>['age must be greater than 18']}
You can also use messages
to get a combination of both errors and hints:
result = schema.call(email: 'jane@doe.org', age: '')
result.messages
# {:age=>["must be filled", "must be greater than 18"]}
Learn more about customizing error and hint messages
Injecting External Dependencies
When validation requires external dependencies, like an access to a database or some remote HTTP api, you can set up your schema to accept additional objects as dependencies that will be injected:
schema = Dry::Validation.Schema do
configure do
option :my_thing, MyThing
def some_predicate?(value)
my_thing.is_it_ok?(value)
end
end
end
You can also inject objects dynamically at run-time:
schema = Dry::Validation.Schema do
configure do
option :my_thing
def some_predicate?(value)
my_thing.is_it_ok?(value)
end
end
end
schema.with(my_thing: MyThing).call(input)
Currently
with
will cause all rules to be re-built, so keep in mind the impact on performance