This library officially supports the following Ruby versions:
- MRI
>= 3.0
- jruby
>= 9.4
(not tested on CI)
See LICENSE
file.
Validation library with type-safe schemas and rules
Home Page: https://dry-rb.org/gems/dry-validation
License: MIT License
This library officially supports the following Ruby versions:
>= 3.0
>= 9.4
(not tested on CI)See LICENSE
file.
Add support for Rule::Attr
that will be created from attr(:name)
DSL, the same as Rule::Key
but uses attr reader rather than [] to access the value.
I have the following validator:
key(:region_iso_code) { |v| v.str? && v.size?(1..3) }
it raise the following error:
{:region_iso_code=>[["region_iso_code length must be within 1 - 3", "MOS"]]}
As you can see the input value MOS
doesn't pass validation. Is it bug?
I would be happy to help either disable some of the active RuboCop inspections, or change the code to follow the default inspections.
Do you guys have any preference on how to solve all of these?
Firstly, as you know, I'm a big fan :) And I'm looking to make formalist integrate more closely with dry-validation right now.
In formalist, we work with the dry-validation rules AST in order to incorporate schema rule information into our form elements (with the idea being that front-end form renderers can include matching client-side validation or hint text).
For schemas with nested keys, we rely on a :set
element being present so we can look for that and use it as a why to go deeper into the nested keys. However, it seems like these :set
elements have disappeared with your work since 0.6.0.
For example, with this little example:
class OneLevelSchema < Dry::Validation::Schema
key(:outer) { |outer|
outer.key(:inner, &:filled?)
}
end
one = OneLevelSchema.new
pp one.rules.map(&:to_ary)
On 0.6.0, we see this output:
[[:and,
[[:key, [:outer, [:predicate, [:key?, [:outer]]]]],
[:set,
[:outer,
[[:and,
[[:key, [:inner, [:predicate, [:key?, [:inner]]]]],
[:val, [:inner, [:predicate, [:filled?, []]]]]]]]]]]]]
But on master
, we see this:
[[:and,
[[:key, [:outer, [:predicate, [:key?, [:outer]]]]],
[:and,
[[:key, [:inner, [:predicate, [:key?, [:inner]]]]],
[:val, [:inner, [:predicate, [:filled?, []]]]]]]]]]
Is this a regression/bug, or is the removal of the :set
element here intentional?
Hi!
First of all, thanks for the great gem! I hope AciverRecord will die one day(it's dying hard...).
I'd like to know, s there a way to programatically figure out which predicate failed?
Result of validation looks like this:
{ :email => ["email must be filled"] }
Basically, it's the same of ActiveModel::Errors#messages
returns: attribute and human readable messages.
The use case where it is not enough: http API, where every type of validation error may result into different code.
I am developing JSON API, and it would be greate to give to API consumers ability to distinguish between different validation failure, because they may want to handle it in different way.
In general, result in such format would be enough:
{
:email => {
:filled => "email must be filled"
}
}
Thanks!
Hello ๐
I'm following the instructions from examples/form.rb
require 'bundler/setup'
require 'dry-validation'
require 'dry/validation/schema/form'
class UserFormSchema < Dry::Validation::Schema::Form
key(:email) {|value| value.str? & value.filled? }
key(:age) {|value| value.int? & value.gt?(18) }
end
schema = UserFormSchema.new
errors = schema.call({'email' => '', 'age' => '18'})
puts errors.inspect
puts ""
puts errors.messages.inspect
__END__
#<Dry::Validation::Schema::Result params={:email=>"", :age=>18} messages={:age=>[["age must be greater than 18", "age must be an integer"], 18], :email=>[["email must be filled"], ""]}>
{:age=>[["age must be greater than 18", "age must be an integer"], 18], :email=>[["email must be filled"], ""]}
There is only one violation for age
: 18
isn't greater than 18
. But it also reports the other error "age must be an integer"
.
If I do:
errors = schema.call({'email' => '', 'age' => '19'})
__END__
#<Dry::Validation::Schema::Result params={:email=>"", :age=>19} messages={:email=>[["email must be filled"], ""]}>
{:email=>[["email must be filled"], ""]}
It accepts the string representation of the age "19"
as an integer.
Add some way to generate readable error messages (for dev/debug purposes), inspecting the output error set is hard when there is many rules and nested input.
Based on example in README
module MyPredicates
include Dry::Validation::Predicates
predicate(:uuid?) do |input|
! /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.match(input).nil?
end
end
class Schema < Dry::Validation::Schema
configure do |config|
config.predicates = MyPredicates
end
key(:id) { |value| value.str? & value.uuid? }
end
When I am writing:
Schema.new.messages({ id: "aaa" })
I always receive:
NoMethodError: undefined method `visit_uuid?' for #<Dry::Validation::ErrorCompiler:0x007f83c40c31f0>
Given the example from the Readme (which was working in 0.2):
require 'dry-validation'
class Schema < Dry::Validation::Schema
key(:email) { |email| email.filled? }
key(:age) do |age|
age.none? | (age.int? & age.gt?(18))
end
end
schema = Schema.new
errors = schema.call(email: '[email protected]', age: nil).messages
puts errors.inspect
# []
The errors
variable should be []
but is nil
on 0.3.0
Hi @solnic!
I'm using dry-validation to validate JSON body of my api and is working great!
I'm in doubt on one specific scenario, for example:
"payments": [
{
"method": "billet",
"amount": 50.85
},
{
"method": "credit_card",
"amount": 134.9,
"installments_quantity": 5,
"installments_tax": 3.9,
"installments_amount": 26.98
}
]
When method
is credit_card
, i need to validate installments
fields as required. Do you have any idea of how can i validate this? My schema source is:
RequiredString = ->(value) { value.filled? & value.str? }
RequiredAmount = ->(value) { value.filled? & value.float? }
key("payments") do |payments|
payments.array? do
payments.each do |payment|
payment.hash? do
payment.key("method", &RequiredString)
payment.key("amount", &RequiredAmount)
end
end
end
end
Could you please explain how to implement exactly_one_of validation?
Is it possible to implement such validation with rule
predicate?
Based on the feedback in #21
With a schema like this, which has an each
block with rules for 2 nested keys:
schema = Dry::Validation.Form do
key(:reviews).each do
key(:summary).required
key(:rating).required(:int?)
end
end
When I call it with a totally empty object, I get the error back for only one of those keys, when it should really be showing me errors for both (summary
and rating
):
schema.("reviews" => [{}]).messages
# => {:reviews=>{0=>{:summary=>["is missing"]}}}
We just have 8 basic predicates that I wrote pretty much only for testing purposes, but obviously we need a ton more to cover common functionality, like (from the top of my head):
size?(num)
- matches exact sizeformat?(regex)
- matches a string against a regexpinclusion?(list)
- checks if a given value is included in the provided list (although I should say this feels too low level and typically I'd probably prefer a custom predicate that would re-use this one)lt?(num)
- matches if a given input is less than the specified onelte?(num)
- matches if a given input is less than or equal to the specified onegte?(num)
- self-explanatoryeql?(anything)
- matches exact equalitywithin?(range)
- matches if the input is within the given rangeI'm inclined to provide higher-level predicates too, like email?
etc.
Please tell me if something is missing :)
/home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit': undefined method `visit_#<Dry::Validation::Schema::Rule:0x00000001ee9098>' for #<Dry::Validation::RuleCompiler:0x00000001ee8648> (NoMethodError)
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:38:in `visit_each'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:48:in `visit_and'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:48:in `visit_and'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:18:in `visit'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:13:in `block in call'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:13:in `map'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/rule_compiler.rb:13:in `call'
from /home/zack/.rvm/gems/ruby-2.2.1/gems/dry-validation-0.2.0/lib/dry/validation/schema.rb:51:in `initialize'
from r.rb:26:in `new'
from r.rb:26:in `<main>'
require 'json'
require 'dry-validation'
class StatSchema < Dry::Validation::Schema
key(:stats) do |root|
root.filled?
root.hash? do
root.each do |s|
s.hash?
s.key(:strength, &:filled?)
end
end
end
end
json = JSON.parse <<-EOT
{
"stats": {
"strength": {
"description": "Increases physical damage"
}
}
}
EOT
schema = StatSchema.new
Any help would be great, thanks!
We are generating validation rules by converting our in-house schema to the dry-validation rule AST and then compile it into a an array of rules. Basically we want to have our rules in a Dry::Validation::Schema[::Form] object so we can reuse the validation process and error compilation that dry-validation provides.
Currently there is no way to do it properly, our options is to duplicate whatever Dry::Validation::Schema does or resort to monkey patching.
If I have a schema like this:
class MySchema < Dry::Validation::Schema
confirmation(:password)
end
And call it with invalid confirmation data:
v = MySchema.new.call(password: "foo", password_confirmation: "bar")
Then it looks like it's not possible to provide YML or i18n data to provide a human-friendly error message for that validation failure.
The reason for this is that the error AST entry for that failure has a :group
in it:
pry(main)> v.errors.map(&:to_ary)
# => [[:error, [:input, [:password_confirmation, ["foo", "bar"], [[:group, [:password_confirmation, [:predicate, [:eql?, []]]]]]]]]]
And the ErrorCompiler
has no #visit_group
method, which means it goes through to #method_missing
and just returns {value: args[1]}
output for the validation result's #messages
, i.e.
pry(main)> v.messages
# => {:password_confirmation=>[[{:value=>:password_confirmation}], ["foo", "bar"]]}
Because it goes through #method_missing
in the error compiler, it means it never actually tries to look up an i18n/yml key for the translated error message for that validation failure.
Is my understanding of this correct? Is this just a matter of updating the ErrorCompiler
to support groups? Or have I missed some already existing way to actually provide human-friendly error messages for failures like this?
Thanks :)
Hello!
In your example of validation elements in array we can't get index of element in array where is error.
How I can get index?
class Schema < Dry::Validation::Schema
key(:phone_numbers) do |phone_numbers|
phone_numbers.array? do
phone_numbers.each(&:str?)
end
end
end
schema = Schema.new
errors = schema.call(phone_numbers: ['123456789', 123456789]).messages
puts errors.inspect
# {
# :phone_numbers => [
# {
# :phone_numbers => [
# ["phone_numbers must be a string", 123456789] << Error in element with index 1
# ]
# }
# ]
# }
Array validation is expected to return validation error if value is not an array, but now it crashes.
dry-validation 0.4.1
dry-data 0.4.2
class Schema < Dry::Validation::Schema::Form
key(:email) { |email| email.filled? }
key(:ids) do |v|
v.array? do
v.each(&:int?)
end
end
end
# works
Schema.new.call({
"email" => "filled",
"ids" => ["1", "2", "3"]
})
# expected to return validation error bug crashes
Schema.new.call({
"email" => "filled",
"ids" => "not_ids"
})
# /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/array.rb:6:in `constructor': undefined method `map' for "not_ids":String (NoMethodError)
# from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type.rb:74:in `call'
# from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:22:in `block in symbolized_constructor'
# from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:18:in `each'
# from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:18:in `each_with_object'
# from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type/hash.rb:18:in `symbolized_constructor'
# from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-data-0.4.2/lib/dry/data/type.rb:74:in `call'
# from /Users/kir/Projects/opensource/dry-params/vendor/bundle/gems/dry-validation-0.4.1/lib/dry/validation/schema/form.rb:15:in `call'
# from bug.rb:23:in `<main>'
Right now only Validation::Predicates
module is the container for built-in predicates but we need a way to define our own predicates too and use them in the schema objects.
It would really help to see some examples of expected usage - not just how to define a schema, but how to consume a schema. All of the examples just show a call to #messages
. Is that the intended API for determining if the input passed schema validation? If not, what is?
Also, it would be useful to include the expected output of the commands in the examples
directory. The output from using those examples is very surprising to me, so I'm wondering if they are behaving as expected. For example, consider https://github.com/dryrb/dry-validation/blob/6447302f3b53766b29f29230831890a5cc3822e0/examples/form.rb
When I run that script I see:
> errors = schema.call('email' => '', 'age' => '18').messages
# => {:age=>[["age must be greater than 18", "age must be an integer"], 18], :email=>[["email must be filled"], ""]}
So, I was curious what valid input would look like. Unfortunately, it looks very similar:
> errors = schema.call('email' => '[email protected]', 'age' => 18).messages
# => {:age=>[["age must be greater than 18", "age must be an integer"], 18]}
Is this expected behavior?
Is it possible to create a white list of keys for a hash? I'm using DryValidation instead of StrongParameters and it's sometimes fine, but in some other places I have to ensure that only whitelisted attributes are allowed to be passed. Basically that's what params[:foo].permit(:bar, :baz)
does.
I'd like to provide validation support for the form builder gem that I'm developing. It's easy enough just to run the form's data through a dry-validation schema after it's been posted, but that's only half of the story. The other half is providing the form builder's UI layer with information about any validation rules before they've been applied โ this would allow it to display validation hints as well as perform some simple validations client-side with JS.
I know you've mentioned the idea of "validation hints" before, @solnic, and I'm guessing this is somewhat related. Do you think it might be worthwhile exposing some AST of the validation schema before it's been called with data? Then we could write an AST compiler that associates validation rule info along with each of the fields we display in our forms.
I'd be happy to have a go at exposing the AST as a first step, if you think this is a reasonable feature to support.
Otherwise high-level rules won't work with predicates that are also Object' methods, ie
eql?`
require 'bundler/setup'
require 'pry'
require 'dry-validation'
require 'dry/validation/schema/form'
class Schema < Dry::Validation::Schema
key(:phone_numbers) do |phone_numbers|
phone_numbers.array? do
phone_numbers.each(&:str?)
end
end
end
schema = Schema.new
# works
class FormSchema < Dry::Validation::Schema::Form
key(:phone_numbers) do |phone_numbers|
phone_numbers.array? do
phone_numbers.each(&:str?)
end
end
end
schema = FormSchema.new
# /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:34:in `visit': undefined method `visit_each' for #<Dry::Validation::InputTypeCompiler:0x007fde6d48e768> (NoMethodError)
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:47:in `block in visit_and'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:47:in `map'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:47:in `visit_and'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:34:in `visit'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:44:in `block in visit_and'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:44:in `map'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:44:in `visit_and'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:34:in `visit'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:29:in `block in call'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:29:in `map'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/input_type_compiler.rb:29:in `call'
# from /Users/kir/Projects/project/vendor/bundle/ruby/2.2.0/gems/dry-validation-0.4.0/lib/dry/validation/schema/form.rb:11:in `initialize'
# from bug.rb:24:in `new'
# from bug.rb:24:in `<main>'
Is there a way? Ot will it be possible to merge rules or extend? Right now inheritance overwrite all rules.
Thanks for dry-validation! Can you provide access to others field values (e.g. value(key)) for something like this? Or is it already possible?
key(:start_date) { |date| date.filled? & date.date? }
key(:end_date) { |date| date.filled? & date.date? }
key(:date_of_issue) { |date| date.filled? & date.date? & date.in_period? }
def in_period?(value)
# start_date = coerced_value(:start_date)
# end_date = coerced_value(:end_date)
# date = coerced_value(:date_of_issue)
# value(key) returns raw value
start_date = Date.parse(value(:start_date))
end_date = Date.parse(value(:end_date))
date = Date.parse(value)
(start_date..end_date).include?(value)
end
Hi @solnic ,
consider such schema:
schema = Dry::Validation.Schema do
key(:nested).each do
key(:name).required
key(:age).required
end
end
p schema.({nested: [{ name: 'Jane', age: 21 }, { name: nil, age: nil }, {name: nil, age: nil}]}).messages
# => {:nested=>{1=>{:name=>["must be filled", "must be filled"], :age=>["must be filled", "must be filled"]}}}
schema = Dry::Validation.Schema do
each do
key(:name).required
key(:age).required
end
end
p schema.([{ name: 'Jane', age: 21 }, { name: nil, age: nil }, {name: nil, age: nil}]).messages
# => {1=>{:name=>["must be filled", "must be filled"], :age=>["must be filled", "must be filled"]}}
In both cases it returns probably first invalid index, but do not continue with the remaining indexes (or continues, but pushes messages into first found?).
With a schema like this, with a single rule inside a single level of nesting:
schema = Dry::Validation.Schema do
key(:meta) do
hash? do
key(:pages).required(:int?)
end
end
end
When I call it with invalid data, the error messages are not properly nested:
schema.(meta: {pages: "123"}).messages
# => {:meta=>["must be an integer"]}
FWIW, if I set the meta
hash without any child keys, the error messages are properly nested:
schema.(meta: {}).messages
# => {:meta=>{:pages=>["is missing"]}}
This only seems to be a problem for keys inside a hash?
block. If I use a nested schema instead:
schema = Dry::Validation.Schema do
key(:meta).schema do
key(:pages).required(:int?)
end
end
Then the error messages come back properly:
schema.(meta: {pages: "123"}).messages
# => {:meta=>{:pages=>["must be an integer"]}}
class Schema < Dry::Validation::Schema
key(:id) { |id| id.nil? | (id.str? & id.format?(UUID)) }
end
I want to check if data hash doesn't have id or when it has then id should be uuid, so id should be in UUID format. I used str? because I checked situation when id was number too. format? method with number param raises exception.
Unfortunately when I had written above code I received:
undefined method `to_ary' for true:TrueClass
I checked backtrace and it looks:
undefined method `to_ary' for true:TrueClass
/Users/wafcio/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/dry-validation-0.1.0/lib/dry/validation/schema/key.rb:26:in `method_missing'
/Users/wafcio/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/dry-validation-0.1.0/lib/dry/validation/schema/definition.rb:6:in `key'
Checked this problem deeply and there is problem with simple nil? predicate
class Schema < Dry::Validation::Schema
key(:id) { |id| id.nil? }
end
it returns
NoMethodError: undefined method `to_ary' for false:FalseClass
from /Users/wafcio/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/dry-validation-0.1.0/lib/dry/validation/schema/key.rb:31:in `method_missing'
Looks like there's an issue preventing form schemas from being instantiated. I get this when I copy/paste/run your examples/form.rb
code:
class UserFormSchema < Dry::Validation::Schema::Form
key(:email) { |value| value.str? & value.filled? }
key(:age) { |value| value.int? & value.gt?(18) }
end
[12] pry(main)> schema = UserFormSchema.new
NoMethodError: undefined method `visit_string' for #<Dry::Data::Compiler:0x007fa59d915c68 @registry=Dry::Data>
from /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'
I'm using the latest from github on both dry-validation and dry-data. Full stacktrace here:
Exception: NoMethodError: undefined method `visit_string' for #<Dry::Data::Compiler:0x007fac72da9820 @registry=Dry::Data>
--
0: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'
1: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:39:in `visit_key'
2: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'
3: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:34:in `block in visit_hash'
4: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:34:in `map'
5: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:34:in `visit_hash'
6: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:22:in `visit_type'
7: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:15:in `visit'
8: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-data-6aa35ff8de9c/lib/dry/data/compiler.rb:11:in `call'
9: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-validation-fb344cc0b4d4/lib/dry/validation/input_type_compiler.rb:19:in `call'
10: /Users/tim/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/bundler/gems/dry-validation-fb344cc0b4d4/lib/dry/validation/schema/form.rb:11:in `initialize'
11: (pry):8:in `new'
12: (pry):8:in `<main>'
I'm just getting started with trying out dry-validation, so I hope this is enough info for you to go on!
Similar to Schema::Form
but with JSON-specific coercions. This will require dry-data json types, which are not done yet.
class MailboxQuery < Dry::Validation::Schema::Form
optional(:per_page) { |pp| pp.int? & pp.gt?(0) & pp.lteq?(100) }
key(:page) { |p| p.int? & p.gt?(0) }
end
/validation/input_type_compiler.rb:23:in `visit': undefined method `visit_implication' for #<Dry::Validation::InputTypeCompiler:0x007f9e9932b3f8> (NoMethodError)
Probably related to #16 somehow.
class PagingValidator < Dry::Validation::Schema::Form
optional(:per_page) { |pp| pp.int? & pp.gt?(0) & pp.lteq?(100) }
optional(:page) { |p| p.int? & p.gt?(0) }
end
result = PagingValidator.new.messages({'per_page' => '10'})
#<Dry::Validation::Schema::Result params={:per_page=>nil} errors=[[:per_page, ["per_page must be an integer"]]]>
class Schema < Dry::Validation::Schema
key(:name) { |n| n.filled? }
key(:data) do |data|
data.key(:invoice_address) do |invoice_address|
%i[street zip city country].each do |f|
invoice_address.key(f, &:filled?)
end
end
end
schema = Schema.new
schema.call({
data: {
invoice_address: {}
}
})
/vendor/bundle/bundler/gems/dry-validation-57d86bbfc390/lib/dry/validation/rule.rb:61:in `block in call': undefined method `call' for :street:Symbol (NoMethodError
class MySchema < Dry::Validation::Schema::Form
key(:name)
key(:year){ |y| y.int? & y.gt(2000) }
end
schema = MySchema.new
schema.call('name' => 'kwando', 'year' => '2016').params # => {year: 2016}
In this case I would expect the key :name
to be present in the params hash and whatever value passed in with the 'name'
key should just be passed through.
Currently we can work around this "issue" by always specify a type for all keys.
class MySchema < Dry::Validation::Schema::Form
key(:name, &:str?)
key(:year){ |y| y.int? & y.gt(2000) }
end
schema = MySchema.new
schema.call('name' => 'kwando', 'year' => '2016').params # => {name: 'kwando', year: 2016}
Optional keys
We can assume that we have users table which has phone columns. Value for phone can be null but when it isn't then it should be valid with some regex (for example US phone format). Based on this short description these hashes should be valid:
{ name: "John Doe" }
{ name: "John Doe", phone: nil }
{ name: "John Doe", phone: "1-234-567-8901" }
but this hash shouldn't be valid:
{ name: "John Doe", phone: "abcd" }
When we define keys in schema class then each key must exists in hash. Sometime we have hash without phone key, thats why I will be very helpful to have optional keys
Another example with optional keys:
Based on JSON API in request we can have filter, sort, ... params. I want use dry-validation to validate income params. In request params optional keys will be very helpful.
{ sort: "email" }
{ filter: { kind: "member" }, sort: "name" }
It can be bypass by merge hash with defult hash structure where we have all keys but value for each key is nil.
What's the recommended way(if possible) to validate aggregate values on a collection? Say I want the sum of all amounts in a nested collection to equal a value of the main object, or there should be at least n items in the collection with property y.
Hi @solnic ,
while validating schema against nested array with just one key defined, e.g:
require 'bundler'
Bundler.require
schema = Dry::Validation.Form do
key(:nested).each do
key(:name).required
end
end
p schema.({"nested" => [{ "name" => 'Jane', age: 21 }, { name: 'Joe', age: nil }]}).messages
it returns:
/media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit': undefined method `visit_n' for #<Dry::Types::Compiler:0x0000000284dd78 @registry=Dry::Types> (NoMethodError)
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:11:in `call'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:38:in `visit_form_array'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:23:in `visit_type'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:53:in `visit_key'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:58:in `block in merge_with'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:58:in `map'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:58:in `merge_with'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:48:in `visit_form_hash'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:23:in `visit_type'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:15:in `visit'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-types-0.6.0/lib/dry/types/compiler.rb:11:in `call'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/input_processor_compiler.rb:16:in `call'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/schema.rb:104:in `input_processor'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/schema.rb:127:in `default_options'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation/schema.rb:45:in `new'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation.rb:36:in `Schema'
from /media/slave/work/.rvm/gems/ruby-2.2.4@isa/gems/dry-validation-0.7.0/lib/dry/validation.rb:41:in `Form'
However, if there's more than one key given, it spins fine.
If you have an app with i18n configured, and some local i18n keys to replace dry-validation message defaults, then later requiring dry-validation will overwrite those keys.
For example, if I set up i18n like this, in the boot sequence of my app:
require "i18n"
I18n.load_path += Dir["#{MyApp::Container.root}/apps/*/locales/**/*.yml"]
I18n.backend.load_translations
And where I have a .yml file like this:
en:
errors:
filled?: "Fill this thing, yo"
And then later on require a file that requires dry-validation, the errors.filled?
key gets replaced with the default:
[1] pry(main)> I18n.t("errors.filled?")
=> "%{name} must be filled"
If, instead, I do a little hoop-jumping and force dry-v's i18n integration to load before I suppose my own locale files, then things work as I'd want:
require "i18n"
# Load dry-validation's i18n support first, so we can override if we want
require "dry/validation/messages/i18n"
I18n.load_path += Dir["#{Alpinist::Container.root}/apps/*/locales/**/*.yml"]
I18n.backend.load_translations
[1] pry(main)> I18n.t("errors.filled?")
=> "Fill this thing, yo"
You can see these examples in action in https://github.com/icelab/alpinist/tree/sign-in-and-users-crud.
I think the preferred behaviour here would be for dry-v not to do anything that might result in pre-existing keys being overwritten.
If I define a form schema like this:
schema = Dry::Validation.Form do
key(:meta) do
hash? do
key(:pages).required(:int?)
end
end
end
I see this error:
NoMethodError: undefined method `visit_p' for #<Dry::Types::Compiler:0x007f9863aa0ca0 @registry=Dry::Types>
Did you mean? visit
from /Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-types-422489afe640/lib/dry/types/compiler.rb:15:in `visit'
FWIW, it works fine if I change it to be a nested schema instead of a hash?
predicate:
schema = Dry::Validation.Form do
key(:meta).schema do
key(:pages).required(:int?)
end
end
Currently the field name is not translated in the error messages you get from calling .messages
from dry-validation. This is most likely needed in order to display those errors to end users.
I'm not sure how this should work, but I think it would be good if the attribute name lookup is somewhat configurable. Sometimes you might have the data needed in the db, hardcoded in your app, in locale files etc.
Hi @solnic !
When running validation on a rule depending on another, specifically this example, it crashes with the following exception NoMethodError: undefined method '[]' for nil:NilClass
, when Schema.new
is fired.
Backtrace:
from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:77:in `visit_predicate'
from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:18:in `visit'
from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:106:in `visit_group'
from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:18:in `visit'
from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:13:in `block in call'
from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:13:in `map'
from gems/dry-logic-0.1.4/lib/dry/logic/rule_compiler.rb:13:in `call'
from gems/dry-validation-0.6.0/lib/dry/validation/schema.rb:83:in `initialize'
The spec/integration/rule_groups_spec.rb
also fails with the same error, however switching dry-logic
to v0.1.2 seems to fix the problem.
Currently the DSLs instance_eval the code block. This is fine in most cases, however it becomes awkward in certain contexts.
For example, say I want to generate an deeply nested hash validation programmatically.
The structure I'm trying to create would look like this:
Dry::Validation.Schema do
key(:Deeply).schema do
key(:Nested).schema do
key(:Object).required(&:string?)
end
end
end
A failed attempt might look like this:
# In my initializer
@path = %w(Deeply Nested Object)
# ....
Dry::Validation.Schema do
@path.reverse.reduce(self) do |memo, segment|
key(:Deeply).schema
end
end
instance_eval
changes self
, so instance variables, and methods in your current object become unreachable. Therefore @path
is nil
(hopefully...).
@path = [:Deeply, :Nested, :Object]
Dry::Validation.Schema do |schema|
@path.reverse.reduce(schema) do |s, segment|
s.key(segment).schema { |nested| s = nested }
s
end
schema
end
Assuming the block is optional, and the return value is the schema you just created; one solution might look like this:
@path = [:Deeply, :Nested, :Object]
Dry::Validation.Schema do |schema|
@path.reverse.reduce(schema) do |s, segment|
s.key(segment).schema
end
end
I think we can implement this so both styles are possible, with changing the API. Perhaps something like this could work:
if block.arity == 1
res = block.call(key)
else
res = key.instance_eval(&block)
end
To keep the schema definitions concise, we can provide various macros to cover common use cases.
Here's an awesome API idea from @fran-worley:
class BarcodeSchema < Dry::Validation::Schema
key(:barcode) do |barcode|
barcode.none? | barcode.filled?
end
key(:job_number) do |job_number|
job_number.none? | job_number.int?
end
key(:sample_number) do |sample_number|
sample_number.none? | sample_number.int?
end
rule(:barcode_only) do
rule(:barcode).filled? & (rule(:job_number).none? & rule(:sample_number).none?)
end
end
# becomes
class BarcodeSchema < Dry::Validation::Schema
key(:job_number).maybe(type: :int)
key(:sample_number).maybe(type: :int)
key(:barcode).maybe.on(:filled) do
key(:job_number, &:none?)
key(:sample_number, &:none?)
end
end
High-level rules described here don't seem to work in the latest version (0.6.0 ATM). Copypaste of BarcodeSchema from that page throws following error:
[4] pry(main)> BarcodeSchema.new.call({})
NoMethodError: undefined method `input' for #<Dry::Logic::Rule::Conjunction:0x0055e497bb7c28>
from /home/ineu/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/dry-logic-0.1.4/lib/dry/logic/rule/check.rb:8:in `evaluate_input'
Say I have Orders and Users to validate, each of which contain an address:
{
username: 1,
address: {
street: '123 Main',
city: 'Seattle'
}
}
{
product_id: 1,
address: {
street: '123 Main',
city: 'Seattle'
}
}
it's not clear from the documentation how I could share the address validation between the two schemas.
README has the following example:
key(:age) { |value| value.int? & value.gt?(18) }
...
errors = schema.call('email' => '', 'age' => '18').messages
...
# :age => [["age must be greater than 18 (18 was given)", 18]]
...
As you can see 18
declared as string value but validation recognize it as integer value and validate it. Seems like this is don't work now (don't recognize integer value in string and raise error: age must be an integer
).
Is it bug in validation or README?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.