dry-validation
dry-validation copied to clipboard
Calling a macro on a deeply nested rule
I'd like to be able to specify a macro in the following manner: rule(foo: :bar).each
where both foo
and bar
are arrays.
Examples
As an example please have a look at this spec that's currently failing: https://github.com/dry-rb/dry-validation/compare/master...DawidJanczak:macro-on-deeply-nested-rule
Resources
Discussed with @solnic here: https://discourse.dry-rb.org/t/calling-a-macro-on-a-depply-nested-rule/842
I just hit the same kind of issue, where my params look like:
{
"first_name": "Francois",
"last_name": "Beausoleil",
"email_locators": [
{
"name": "professional",
"email": "[email protected]"
}
]
}
I have a macro named email_format
, which I'd like to apply to the email fields on the nested locators, but this fails:
class ApplicationContract < Dry::Validation::Contract
config.messages.backend = :i18n
register_macro(:email_format) do
if value.present? && !/\A[-\w+.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
key.failure(:email_format)
end
end
end
class ManuallyRegisterPersonContract < ApplicationContract
params do
required(:first_name).maybe(Types::StrippedString, min_size?: 1)
required(:last_name).maybe(Types::StrippedString, min_size?: 1)
required(:date_of_birth).maybe(:date)
required(:email_locators).maybe(:array).each do
hash do
required(:name).filled(Types::StrippedString, min_size?: 1)
required(:email).filled(Types::StrippedString, min_size?: 1)
end
end
optional(:metadata).hash(MetadataContract)
end
rule(email_locators: [:email]).validate(:email_format)
end
RSpec.describe ManuallyRegisterPersonContract do
describe "a contract with one email locator that has an invalid email address format" do
fit "fails validation" do
params = {
first_name: nil,
last_name: nil,
date_of_birth: nil,
email_locators: [
{
name: "personal",
email: "baden@"
}
]
}
result = subject.call(params)
expect(result).to be_failure
end
end
end
1) ManuallyRegisterPersonContract a contract with one email locator that has an invalid email address format fails validation
Failure/Error: if value.present? && !/\A[-\w+.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
NoMethodError:
undefined method `fetch_values' for #<Dry::Validation::Values:0x00007fe255003518>
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/values.rb:98:in `method_missing'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/values.rb:57:in `[]'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/evaluator.rb:151:in `value'
# ./app/models/application_contract.rb:7:in `block in <class:ApplicationContract>'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/evaluator.rb:82:in `instance_exec'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/evaluator.rb:82:in `block in initialize'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/evaluator.rb:80:in `each'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/evaluator.rb:80:in `initialize'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/rule.rb:38:in `new'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/rule.rb:38:in `call'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/contract.rb:98:in `block (2 levels) in call'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/contract.rb:95:in `each'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/contract.rb:95:in `block in call'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/result.rb:25:in `new'
# /Users/francois/.rvm/gems/ruby-2.7.2/bundler/gems/dry-validation-9b03b9ead558/lib/dry/validation/contract.rb:94:in `call'
# ./spec/people/manually_register_person_contract_spec.rb:59:in `block (3 levels) in <main>'
# ./spec/rails_helper.rb:23:in `block (3 levels) in <top (required)>'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/database_cleaner-sequel-1.8.0/lib/database_cleaner/sequel/transaction.rb:36:in `block in cleaning'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/sequel-5.37.0/lib/sequel/database/transactions.rb:251:in `_transaction'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/sequel-5.37.0/lib/sequel/database/transactions.rb:233:in `block in transaction'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/sequel-5.37.0/lib/sequel/connection_pool/threaded.rb:92:in `hold'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/sequel-5.37.0/lib/sequel/database/connecting.rb:270:in `synchronize'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/sequel-5.37.0/lib/sequel/database/transactions.rb:195:in `transaction'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/database_cleaner-sequel-1.8.0/lib/database_cleaner/sequel/transaction.rb:36:in `cleaning'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/database_cleaner-1.8.5/lib/database_cleaner/configuration.rb:87:in `block (2 levels) in cleaning'
# /Users/francois/.rvm/gems/ruby-2.7.2/gems/database_cleaner-1.8.5/lib/database_cleaner/configuration.rb:88:in `cleaning'
# ./spec/rails_helper.rb:22:in `block (2 levels) in <top (required)>'
I fixed my immediate problem by using the solution in Calling a macro on a depply nested rule. It would be really nice if dry-validation handled it for us.
So, for that, we want to come up with an explicit key path syntax that points to arrays. It's partially implemented in some places in dry-schema but we really need to properly encapsulate it in Dry::Schema::Path
so that parsing and processing such paths is simple. In general, handling key paths must be improved and cleaned up (both in validation and schema), thus I'm scheduling this for 2.0.0.