ndan
May 18, 2019, 10:31am
1
I’m moving validation from ActiveRecord to dry-validation and I need to validate nested hash with dynamic keys:
{
...
"users_attributes" => {
"0" => { "name" => "John Doe" },
"1" => { "name" => "Dan Brown" },
...
}
}
I don’t care about keys (eg “0”, “1”), but I’d like to validate each value (eg required(:name).filled(:string)
)
Is there any way to do so with dry-schema (>= v1.0.2) and/or dry-validation (>= v1.0.0.rc3)?
This is currently not supported by the schema. I’ll add support for dynamic keys in 1.x
but for now you could work around this limitation by using contract rules:
class MyContract < Dry::Validation::Contract
UserAttribute = Dry::Schema.Params do
required(:name).filled(:string)
end
params do
required(:users_attributes).filled(:hash)
end
rule(:users_attributes) do
values[:users_attributes].each do |key, value|
res = UserAttribute.(value)
next if res.success?
key([:users_attributes, key]).failure(res.to_h)
end
end
end
1 Like
ndan
May 20, 2019, 8:04am
3
Thanks, unfortunately key([:users_attributes, key]).failure(res.to_h)
doesn’t work.
The only solution I found:
res.errors.to_h.each do |name, messages|
messages.each do |msg|
key([key_name, name]).failure(msg)
end
end
The downside of rule(:user_attributes)
that it won’t remove unknown keys
attrs = {
"users_attributes" => {
"0" => { "wrong_key" => 0, "name" => "John Doe" },
}
}
MyContract.new.(attrs).to_h
=> {:users_attributes=>{"0"=>{"unknown_key"=>0, "name"=>"John Doe"}}}
Is there any way to remove unknown_key
from the result, so I can get rid of strong_parameters
?
Ah right, key processing won’t be triggered at all in such a case. I’m afraid this really has to wait until we have proper support for dynamic keys
ndan
May 20, 2019, 9:08am
5
Got it,
It seems the best solution, for now, is to convert hashes into array
class MyContract < Dry::Validation::Contract
params do
required(:users_attributes).array(:hash) do
required(:name).filled(:string)
end
end
end
attrs = {
"users_attributes" => {
"0" => { "unknown_key" => 0, "name" => "John Doe" },
"1" => { "a" => "Dan Brown" },
}.values
}
result = MyContract.new.(attrs)
result.to_h # => {:users_attributes=>[{:name=>"John Doe"}, {}]}
result.errors.to_h # => {:users_attributes=>{1=>{:name=>["is missing"]}}}
1 Like