Hey Stas. This question is tangentially similar to what was asked earlier here but I think your question can probably be solved through pattern matching in this situation. Here’s a little script you can tinker with that should demonstrate a potential solution for you:
#! /usr/bin/env ruby
# frozen_string_literal: true
# Save as `snippet`, then `chmod 755 snippet`, and run as `./snippet`.
require "bundler/inline"
gemfile true do
source "https://rubygems.org"
gem "amazing_print"
gem "debug"
gem "dry-schema"
end
module Schemas
User = Dry::Schema.Params { required(:name).filled :string }
Admin = Dry::Schema.Params { required(:email).filled :string }
Unknown = proc { "Unknown parameters." }
end
class Validator
SCHEMAS = {user: Schemas::User, admin: Schemas::Admin, unknown: Schemas::Unknown}.freeze
def initialize schemas: SCHEMAS
@schemas = schemas
end
def call(parameters) = find_schema(parameters).call parameters
private
attr_reader :schemas
def find_schema parameters
case parameters
in name:, **remainder unless remainder.key? :email then schemas.fetch :user
in email:, **remainder unless remainder.key? :name then schemas.fetch :admin
else schemas.fetch :unknown
end
end
end
validator = Validator.new
ap validator.call({name: "Jane Doe"})
ap validator.call({email: "Jill Smith"})
ap validator.call({name: "Invalid", email: "admin@example.com"})
When running the above you’ll see the following output:
You’ll notice that the validator handles a user, admin, and unknown data (simple use case but you can make it as sophisticated as needed). Basically, I’m using pattern matching to delegate to the appropriate schema to determine which schema to validate against. This assumes you have distinguishing keys withing your parameters to route to the appropriate schema, though.
and I wanna it would be correct and invalid only when name and email blank.
Honestly, its for dynamic schema build, but I try to avoid monkey patching DSL or making something like Schema.from_ast 'cause it’s looks like it would take too much time
In this case I have both validation from UserSchema and AdminSchema, and it’s only valid when both of validation passed, but I need, that result contract be valid even if one of schemas passed (like UserSchema || AdminSchema)
Apologies, I misunderstood your question. What you’re trying to achieve was intended to be done with Contract objects, not schemas.
In this case, you cannot reuse UserSchema and AdminSchema to do this because you need to change the semantics of the schema to express this rule.
At the schema level, both keys must be optional because one or the other can be missing. The rule would be:
my_contract = Dry::Validation::Contract.build do
params do
optional(:name).filled(:string)
optional(:email).filled(:string)
end
rule :name, :email do
unless values[:name] || values[:email]
base.failure("must have one of: name, email")
end
end
end
Stas: Oh, OK, I see what you are saying. I thought you needed to toggle between different schema versions in a sense but I see what you are saying now.
Adam: Thanks. I wasn’t aware you could have parents so that was fun to learn.