Build dry-validations dynamically

Hey guys,

I’ve discovered dry-validation a few days ago and I’m currently trying it out on a project. Now I’ve come to the point where I was thinking about creating schemas dynamically. If I understand correct I can simply create my own schema class with something like:

class Schema < Dry::Validation::Schema::Form
  define! do
    required(:whatever).value(:str?)
  end
end

Let’s say I would like to pass in which fields of my schema are optional since this differs from case to case. Is there any non hacky way to achieve this?

1 Like

We have a rather sophisticated mechanism for building schemas dynamically by using a formalized rule AST (provided by dry-logic). The DSL is a front-end for building that AST, but if somebody wants to define a schema dynamically I would recommend building an AST and then it’s a matter of passing it to schema builder. Please let me know if this is something you’d like to do, I can provide some examples.

1 Like

Thanks for you reply. It’s been a while. But yeah if you would have some examples on how to build an AST, I would appreciate it! I love the idea of dry-validation. Thanks for your amazing work guys!

I would love to see an example for this, too. Thank you.

Wondering, were you able to build schemas dynamically? If yes, could you share an example? Thank you.

I’d be interested some examples as well.

Things changed a lot since 2017 :sweat_smile: The main idea of using an AST is still the same though but there are now various nitty-gritty details that make it a bit hard to instantiate a schema object manually.

In order to make this easy we’d have to add a public API that would take a list of rules and turn that into a schema object. Right now this is as far as you can go:

dry-schema> s = schema { required(:name).value(:string) }
=> #<Dry::Schema::Processor keys=[:name] rules={:name=>"key?(:name) AND key[name](str?)"}>
dry-schema> ast = s.to_ast
=> [:set, [[:and, [[:predicate, [:key?, [[:name, :name], [:input, Undefined]]]], [:key, [:name, [:predicate, [:str?, [[:input, Undefined]]]]]]]]]]
dry-schema> c = Dry::Schema::Compiler.new
=> #<Dry::Schema::Compiler:0x00007fb6acad61b8 @predicates=#<Dry::Schema::PredicateRegistry:0x00007fb6acad6280 @has_predicate=#<Method: Kernel#respond_to?>, @predicates=Dry::Logic::Predicates>>
dry-schema> rules = c.([ast])
=> [#<Dry::Logic::Operations::Set rules=[#<Dry::Logic::Operations::And rules=[#<Dry::Logic::Rule::Predicate::Predicate2Arity1Curried predicate=#<Method: Dry::Logic::Predicates.key?> options={:args=>[:name], :arity=>2}>, #<Dry::Logic::Operations::Key rules=[#<Dry::Logic::Rule::Predicate::Predicate1Arity predicate=#<Method: Dry::Logic::Predicates.str?> options={:args=>[], :arity=>1}>] options={:name=>:name, :evaluator=>#<Dry::Logic::Evaluator::Key path=[:name]>, :path=>:name}>] options={:hints=>false}>] options={}>]

Now, having a list of rules is just not enough to instantiate a schema. A schema uses an internal “step processor” that supports before/after hooks and has three main steps that are applied when a schema validates data, one of those steps is “rule applier” and that is what needs the rules produced by the compiler.

Please remember that I am not saying it’s not possible, I’m just saying it requires a bit of a ceremony :slight_smile: See what the DSL is doing to get an idea of what I’m talking about.

I’m definitely interested in adding a first-class public API to make this very simple though - please feel free to report an issue and I’ll schedule it for 2.0.0 release.

1 Like

I am wondering if anything has changed since 2020 on this?

Looking to build dry validations on the fly for user-defined form fields and validations - a scenario where the activerecord validations cannot be used.

What would be the best approach to building validation rules on the fly and running them?

For those wondering, I figured out how to dynamically build schemas using dry-schemas by calling Dry::Schema::DSL directly.

dsl = Dry::Schema::DSL.new(processor_type: Dry::Schema::Params)
dsl.required(:foo).filled(:string)
dsl.optional(:bar).maybe(:integer)
# ...
params_schema = dsl.call
params_schema.call(params)
# => #<Dry::Schema::Result{...}

I assume you could do something similar with dry-validation?

4 Likes