# Ensuring that only one of n keys is present and valid

**URL:** <https://discourse.dry-rb.org/t/ensuring-that-only-one-of-n-keys-is-present-and-valid/65>\
**Category:** dry-validation\
**Created:** [July 19, 2016, 8:20am UTC](https://discourse.dry-rb.org/t/ensuring-that-only-one-of-n-keys-is-present-and-valid/65 "2016-07-19T08:20:51Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![timriley](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/timriley/32/25_2.png) [@timriley](https://discourse.dry-rb.org/u/timriley)\
**Post date:** [July 19, 2016, 8:20am UTC](https://discourse.dry-rb.org/t/ensuring-that-only-one-of-n-keys-is-present-and-valid/65/1 "2016-07-19T08:20:51Z")

</div>

Today I had a go at writing a schema to model the validation that [Pin Payments’ `/charges` API endpoint](https://pin.net.au/docs/api/charges#post-charges) would need to offer.

One of the more interesting rules it has is that only one piece of card-related info is provided: the post data should have _only one_ of the `card`, `card_token` or `customer_token` keys.

Each one of those keys would have it’s own related validation rules, of course. For example:

```ruby
Dry::Validation.Schema do
  optional(:card).maybe do
    schema do
      required(:number).filled(size?: 16)
    end
  end
  optional(:card_token).maybe(format?: /^card_/)
  optional(:customer_token).maybe(format?: /^cus_/)
end

```

What I’m not sure about is how to write a rule that requires _only one_ of these keys to be actually present. Would any of you know how to do this (and whether it’s currently possible with dry-validation)?

I initially went to create a high-level rule that would operation on the values of `card`, `card_token` and `customer_token`, and then I wanted to somehow put those values into an array, strip away the empty ones, and then check that the array’s length is only one, but I wasn’t sure how I could bundle up all three of those values and pass them to a single predicate.

Thanks for any help! 🙏

---

<div class="post-metadata">

**Author:** ![solnic](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/solnic/32/779_2.png) [@solnic](https://discourse.dry-rb.org/u/solnic)\
**Post date:** [July 21, 2016, 4:46pm UTC](https://discourse.dry-rb.org/t/ensuring-that-only-one-of-n-keys-is-present-and-valid/65/2 "2016-07-21T16:46:56Z")

</div>

Hey Tim,

I guess the new `input` macro could be used in this case, so ie:

```ruby
Dry::Validation.Schema do
  configure do
    def valid_keys?(input)
      input.keys.size == 1
    end
  end

  input :hash?, :valid_keys?

  optional(:card).maybe do
    schema do
      required(:number).filled(size?: 16)
    end
  end

  optional(:card_token).maybe(format?: /^card_/)
  optional(:customer_token).maybe(format?: /^cus_/)
end

```

Lemme know if that works for you.

---

<div class="post-metadata">

**Author:** ![timriley](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/timriley/32/25_2.png) [@timriley](https://discourse.dry-rb.org/u/timriley)\
**Post date:** [August 1, 2016, 1:42am UTC](https://discourse.dry-rb.org/t/ensuring-that-only-one-of-n-keys-is-present-and-valid/65/3 "2016-08-01T01:42:57Z")

</div>

Thanks for the pointer to the `input` macro here, @solnic, it makes perfect sense!

However, the example code you’ve posted is crashing:

```ruby
require "dry-validation"

schema = Dry::Validation.Schema do
  configure do
    def valid_keys?(input)
      input.keys.size == 1
    end
  end

  input :hash?, :valid_keys?

  optional(:card).maybe do
    schema do
      required(:number).filled(size?: 16)
    end
  end

  optional(:card_token).maybe(format?: /^card_/)
  optional(:customer_token).maybe(format?: /^cus_/)
end

input = {
  card: {number: "4200000000000000"},
  customer_token: "cus_1234",
}

result = schema.(input)

p result.messages

```

Running this gives the following error:

```auto
/Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-validation-3d090eeafac9/lib/dry/validation/schema/rule.rb:141:in `and': undefined method `to_ast' for #<UnboundMethod: #<Class:0x007ff15c11e7e8>#valid_keys?> (NoMethodError)
Did you mean? to_s
	from /Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-validation-3d090eeafac9/lib/dry/validation/schema/value.rb:185:in `each'
	from /Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-validation-3d090eeafac9/lib/dry/validation/schema/value.rb:185:in `reduce'
	from /Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-validation-3d090eeafac9/lib/dry/validation/schema/value.rb:185:in `infer_predicates'
	from /Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-validation-3d090eeafac9/lib/dry/validation/schema/class_interface.rb:51:in `define'
	from /Users/tim/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/dry-validation-3d090eeafac9/lib/dry/validation.rb:39:in `Schema'
	from test2.rb:3:in `<main>'

```

I’ll file a bug in dry-v for this.
