Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling a macro on a deeply nested rule #578

Open
DawidJanczak opened this issue Jul 31, 2019 · 2 comments
Open

Calling a macro on a deeply nested rule #578

DawidJanczak opened this issue Jul 31, 2019 · 2 comments

Comments

@DawidJanczak
Copy link
Contributor

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: 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

@solnic solnic added this to the 1.3.0 milestone Jul 31, 2019
@solnic solnic modified the milestones: 1.3.0, 1.4.0 Aug 13, 2019
@solnic solnic modified the milestones: 1.4.0, 1.5.0 Dec 13, 2019
@solnic solnic removed this from the 1.5.0 milestone Mar 11, 2020
@francois
Copy link

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.

@solnic
Copy link
Member

solnic commented Nov 11, 2020

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.

@solnic solnic added this to the 2.0.0 milestone Nov 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants