Hi, I’m currently migrating from dry-validation-0.13 to dry-schema-1.6.1 and couldn’t find a way to add validation rules.
Here’s an example I had when using Dry::Validation
Dry::Validation.Params do
...
rule(role_for_manage_kind: %i[filter_by_kind filter_by_role]) do |filter_by_kind, filter_by_role|
filter_by_kind.eql?('manage').then(filter_by_role.filled?)
end
end
This is basically a conditional validation, if the value of filter_by_kind is equal ‘manage’, the field filter_by_role should be required
Any idea of how I go about this but using “Dry::Schema” instead?
In a pre-1.0 dry-validation version, I had a Dry::Validation.Schema setup which allowed the use of custom methods to validate some fields. I think this can be replicated with rules in Dry::Validation::Contract, but previously we could also reuse/embed the schema around.
Somehow, taking from email examples, I’d like to have something like this:
class ExampleAddress < Dry::Validation::Contract do
schema do
required(:name).filled(:string)
required(:email).filled(:string)
end
rule(:email) do
# ... custom validation
end
end
class ExampleForm < Dry::Validation::Contract do
schema do
required(:message)
required(:to).hash(ExampleAddress)
required(:from).hash(ExampleAddress)
end
end
Of course, that does not work. I’ve navigated the docs of dry-validation and dry-schema but I’ve been unable to find an answer.
Can this be done at all with the 1.6 version, or did the feature disappear?
Currently, Contracts don’t support composition. There is an open issue with an unfinished implementation of this feature, but this is still a work in progress.
Schemas may be composed, though, so I would recommend splitting this up along those lines. You may share rules as macros in 1.6, although this is considered an experimental feature so maybe you don’t want to rely on it.
Dry::Validation.register_macro(:email_format) do
unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
key.failure('not a valid email format')
end
end
EmailAddress = Dry::Schema.Params do
required(:name).filled
required(:email).filled
end
class ExampleForm < Dry::Validation::Contract
schema do
required(:message)
required(:to).schema(EmailAddress)
required(:from).schema(EmailAddress)
end
rule(to: :email).validate(:email_format)
rule(from: :email).validate(:email_format)
end
BTW the website’s docs have a different syntax for this, which fails to compile (validate isn’t defined for the class). I know how docs VS code can be, just thought I’d mention it.
The macro-based approach looks messy to be honest. After playing with it, I know that it won’t work in my particular case because my Form class validates an array of messages, so the macro block gets a nil value in that case. I’m not sure if that’s a bug of just that I’m abusing a feature in a new way.
class MoreForm < Dry::Validation::Contract
register_macro(:email_macro) do
# value => nil
end
schema do
required(:data).array(:hash) do
required(:to).schema(AddressSchema)
required(:from).schema(AddressSchema)
# ...
end
end
rule(to: :email).validate(:email_macro)
end
Reading the docs, I get the sense that you’re encouraged to build a hierarchy of contracts classes, where macros and configuration are inherited. Wouldn’t that be simpler to have plain old ruby methods instead of passing around code blocks in that case?