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

Rule fired for optional attribute which is missing #540

Closed
mateusz-useo opened this issue Jun 11, 2019 · 9 comments

Comments

Projects
None yet
4 participants
@mateusz-useo
Copy link

commented Jun 11, 2019

Description

Rule is getting fired for a required attribute which is nested within an optional attribute and when the whole optional hash is missing (which should be allowed) it reports an error causing the validation to fail.

To Reproduce

Create a contract with a rule for an optional hash attribute with a required key + a rule for the required key and run with an input not including that hash. I use custom macro and I don't know if it's related to the bug.

So given:

class SignUpContract < Dry::Validation::Contract
  params do
    required(:user).hash do
      optional(:id) { filled? & str? }
      optional(:first_name) { filled? & str? }
      optional(:last_name) { filled? & str? }
      required(:email) { filled? & str? }
      required(:password) { filled? & str? }
      required(:accepts_terms) { filled? & bool? }
      optional(:freelancer).hash do
        required(:id) { filled? & str? }
      end
      optional(:clients).array(:hash) do
        required(:id) { filled? & str? }
      end
    end
  end

  rule('user.id').validate(:uuid_format)
  rule('user.email').validate(:email_format)
  rule('user.password').validate(min_length: 8)

  rule(user: %i[freelancer clients]) do
    freelancer = values.dig(:user, :freelancer)
    clients = values.dig(:user, :clients)

    unless freelancer ^ (clients && clients.size == 1)
      key(:user).failure(:profile_missing)
    end
  end

  rule('user.freelancer.id').validate(:uuid_format)
  rule('user.clients') do
    have_valid_uuid = lambda { |client|
      client[:id].nil? || client[:id].match?(Macros::UUID_REGEXP)
    }

    unless value.all?(have_valid_uuid)
      key.failure('not a valid UUID format')
    end
  end
end

And params:

{
  "user": {
    "id": "d16dfef3-50f9-4588-b7d7-837801e403cd",
    "first_name": "Blah",
    "last_name": "Blah",
    "email": "blah@blah.blah",
    "password": "blahblahblah",
    "accepts_terms": true,
    "clients": [
      {
        "id": "d287fdff-78ac-45ed-871b-b1b5c9548399"
      }
    ]
  }
}

It produces:

#<Dry::Validation::Result{:user=>{:id=>"d16dfef3-50f9-4588-b7d7-837801e403cd", :first_name=>"Blah", :last_name=>"Blah", :email=>"blah@blah.blah", :password=>"blahblahblah", :accepts_terms=>true, :clients=>[{:id=>"d287fdff-78ac-45ed-871b-b1b5c9548399"}]}} errors={:user=>{:freelancer=>{:id=>["not a valid UUID format"]}}}>

Expected behavior

The validation rule for optional attribute should never be run when it's missing.
In the example rules related to freelancer attributes should not be run and the validation should pass

My environment

  • Affects my production application: NO as I haven't upgraded yet
  • Ruby version: 2.6.3
  • OS: MacOS Mojave Version 10.14.5 (Darwin Mateuszs-MacBook-Pro.local 18.6.0 Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64 x86_64)

@solnic solnic added this to the 1.0.1 milestone Jun 11, 2019

solnic added a commit that referenced this issue Jun 14, 2019

Add Values#key? and Evaluator#value?
This is helpful when dealing with optional keys. We can't just assume
that we do not want to execute a rule when a value was missing.

Good example: a password must be provided when login is provided, and
both are optional.

Refs #540
@solnic

This comment has been minimized.

Copy link
Member

commented Jun 14, 2019

Please see #550 and let me know if this makes sense to you.

solnic added a commit that referenced this issue Jun 14, 2019

Add Values#key? and Evaluator#value?
This is helpful when dealing with optional keys. We can't just assume
that we do not want to execute a rule when a value was missing.

Good example: a password must be provided when login is provided, and
both are optional.

Refs #540
@solnic

This comment has been minimized.

Copy link
Member

commented Jun 14, 2019

I'm gonna close this one since, after all, this is not a bug. See #550 where new features have been added to make it easier to work with optional keys and rules that depend on them.

@solnic solnic closed this Jun 14, 2019

@solnic solnic removed the 🐛 bug label Jun 14, 2019

@mateusz-useo

This comment has been minimized.

Copy link
Author

commented Jun 17, 2019

It is awesome, it does add the condition here and then more often than not but I do get the idea behind it so thank you, I really appreciate your work :)

@solnic

This comment has been minimized.

Copy link
Member

commented Jun 17, 2019

@mateusz-useo cool! I plan to add a bigger feature soon where you'll be able to use pattern matching to run rules conditionally, so ie this would work:

rule(:numbers).if(:key?).each do
  key.failure("must be even") if value.odd?
end
@flash-gordon

This comment has been minimized.

Copy link
Member

commented Jun 17, 2019

for the record, this thing in PM is usually called "guard", just to save some time on coming with proper naming which is, you know, hard...

@mateusz-useo

This comment has been minimized.

Copy link
Author

commented Jun 26, 2019

Maybe rule? instead of rule(...).if(:key?) as a shorthand?

@solnic

This comment has been minimized.

Copy link
Member

commented Jun 26, 2019

@mateusz-useo oh that's a nice idea, thanks. I'll keep it in mind!

@shepmaster

This comment has been minimized.

Copy link

commented Jul 3, 2019

Please consider how this can work with .validate as well.

@solnic

This comment has been minimized.

Copy link
Member

commented Jul 4, 2019

@shepmaster good point

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.