Often it is not enough to define simple type-checking rules. In addition to those you need to be able to specify higher-level rules that rely on other rules. This can be achieved using the
rule interface which can access already defined rules for specific keys.
For example let's say we have a schema with 3 keys,
:sample_number and we need to make sure that when barcode is provided both job and sample numbers are not provided. The low-level checks need to make sure that individual values have the correct state and on top of those we can define our high-level rule:
schema = Dry::Validation.Schema do required(:barcode).maybe(:str?) required(:job_number).maybe(:int?) required(:sample_number).maybe(:int?) rule(barcode_only: [:barcode, :job_number, :sample_number]) do |barcode, job_num, sample_num| barcode.filled? > (job_num.none? & sample_num.none?) end end
This way we have validations for individual keys and the high-level
:barcode_only rule which says "barcode can be filled only if both job_number and sample_number are empty".
Similar to rules that depend on results from other rules, you can define high-level rules that need to apply additional predicates to values provided by other rules. For example, let's say we want to validate presence of an
login value is set to
schema = Dry::Validation.Schema do required(:login).filled(:bool?) required(:email).maybe(:str?) rule(email_presence: [:login, :email]) do |login, email| login.true?.then(email.filled?) end end
This translates to "login set to true implies that email must be present".
We can also easily specify a rule for the absence of an email:
schema = Dry::Validation.Schema do required(:login).filled(:bool?) required(:email).maybe(:str?) rule(email_absence: [:login, :email]) do |login, email| login.false?.then(email.none?) end end
Notice that you must add the
:email_absence message to the configuration if you want to have the error converted to a message.
When the validity of one attribute depends on the value of another attribute, you can use
schema = Dry::Validation.Schema do required(:started).filled(:date?) required(:ended).filled(:date?) rule(started_before_ended: [:started, :ended]) do |started, ended| ended.gt?(value(:started)) end end schema.call(started: Date.today, ended: Date.today - 1).success? # => false schema.call(started: Date.today, ended: Date.today + 1).success? # => true