# Thoughts on dry-validation wrt: composing validations

**URL:** <https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904>\
**Category:** Ideas\
**Created:** [September 18, 2019, 4:01pm UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904 "2019-09-18T16:01:49Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![i2w](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/i2w/32/30_2.png) [@i2w](https://discourse.dry-rb.org/u/i2w)\
**Post date:** [September 18, 2019, 4:01pm UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/1 "2019-09-18T16:01:49Z")

</div>

I’ve been trying to convert some active record validations to dry-validations 1.x recently and I’ve hit a couple of problems.

1. Sometimes I want to call a **contract** on the input conditionally and it’s not obvious (at least to me) how to do it (it is more obvious how to call a schema conditionally).
2. Sometimes I want to call a contract on nested data, and have the contract’s rules applied to that data as if it had no knowledge of its surrounding context.

My understanding of dry-validations 1.0 is that this is mostly to do with the design decision to validate params (as data) first, then run rules second. I totally agree with this design decision as it makes rule writing much easier.

After trying a few different ways, I decided to hack something completely different together, to explore some ideas. My idea is to have a higher level validation composer, which can specify other contracts to be used on the data, with simple guard clauses.

Here’s some example code from a project:

```ruby
class OrderContract < ApplicationContract
  params do
    required(:delivery_required).value(:bool)
    required(:delivery_address_same).value(:bool)
    required(:accept_terms).value(:bool)
  end

  rule(:accept_terms).validate(:acceptance)

  # this is the new bit - it runs after above schema/rules
  # result values are available to the guard clauses
  compose do
    contract CustomerContract
    contract address: AddressContract

    guard :require_delivery_address? do
      contract delivery_address: AddressContract
    end

    register_guard :require_delivery_address? do
      values[:delivery_required] && !values[:delivery_address_same]
    end
  end
end

```

You can also create a standalone Composed contract, an example from the specs:

```ruby
describe ApplicationContract::Composition do
  let(:composition) do
    ApplicationContract::Composition.new do |c|
      c.contract email_schema
      c.contract name_schema
    end
  end

  describe "#call(input)" do
    subject(:result) { composition.call(input) }

    let(:input) { { email: "foo", name: "jim", other: "foo" } }

    it "returns a result" do
      expect(subject).to be_failure
    end

    it "returns the schema values" do
      expect(result.to_h).to eq(email: 'foo', name: 'jim')
    end

    it "has errors" do
      expect(result.errors.to_h).to eq(email: ['is in invalid format'])
    end
  end
end

```

If anyone’s interested in this, and how it could be better, or if it is suitable for adding to dry-validation, I’d be glad to discuss, and also post the code that makes the above work.

---

<div class="post-metadata">

**Author:** ![i2w](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/i2w/32/30_2.png) [@i2w](https://discourse.dry-rb.org/u/i2w)\
**Post date:** [September 19, 2019, 9:13am UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/2 "2019-09-19T09:13:24Z")

</div>

I’ve made a couple of changes, and put the code for this spike at: [https://gist.github.com/ianwhite/a32fcd439020ea07ed1fe3243152274f](https://gist.github.com/ianwhite/a32fcd439020ea07ed1fe3243152274f)

I’m finding it has solved the problems 1 & 2 above pretty well. In order to come up with a solution I’ve added two new result classes **ResultAtPath** and **ResultSet** , which aid in composition

---

<div class="post-metadata">

**Author:** ![i2w](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/i2w/32/30_2.png) [@i2w](https://discourse.dry-rb.org/u/i2w)\
**Post date:** [September 20, 2019, 3:57pm UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/3 "2019-09-20T15:57:40Z")

</div>

More updates at [this gist](https://gist.github.com/ianwhite/a32fcd439020ea07ed1fe3243152274f)

The composable contract returns a result duck type, which is composed of the contract results

The DSL now looks like this:

```ruby
# expects hash of order data, customer data,
# nested :address, and possibly a nested :delivery_address
class OrderContract < ApplicationContract
  params do
    required(:delivery_required).value(:bool)
    required(:delivery_address_same).value(:bool)
    required(:accept_terms).value(:bool)
  end

  rule(:accept_terms).validate(:acceptance)

  compose do
    contract CustomerContract
    
    path :address do
      contract AddressContract
    end
    
    path :delivery_address do
      contract AddressContract, check: :require_delivery_address?
    end

    register_check :require_delivery_address? do
      values[:delivery_required] && !values[:delivery_address_same]
    end
  end
end  

```

---

<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:** [September 26, 2019, 8:42am UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/4 "2019-09-26T08:42:33Z")

</div>

@i2w I’m very happy that you worked on this because I’ve been thinking about something very similar! I would probably vote for expanding Contract’s DSL rather than introducing a new concept on top of it though. WDYT? I’d be 💯 on board with adding this as an experimental new feature in one of the minor releases with the goal to ship its final version in 2.0.0.

---

<div class="post-metadata">

**Author:** ![i2w](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/i2w/32/30_2.png) [@i2w](https://discourse.dry-rb.org/u/i2w)\
**Post date:** [September 26, 2019, 10:11am UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/5 "2019-09-26T10:11:16Z")

</div>

Hi @solnic, I’m very happy to modify the above to add to the DSL. My reason for making this spike separate was just that it’s easier for me to understand the moving parts. I must say that the API for validation/errors has been a joy to work with when creating this spike.

In terms of a suggested API, how about we remove the enclosing ‘compose’ block, so we have something like:

```ruby
class OrderContract < ApplicationContract
  params do
    required(:delivery_required).value(:bool)
    required(:delivery_address_same).value(:bool)
    required(:accept_terms).value(:bool)
  end

  rule(:accept_terms).validate(:acceptance)

  # also calls the CustomerContract on the input
  contract CustomerContract
    
  # also calls the AddressContract on the input at :path
  path :address do
    contract AddressContract
  end

  # or
  contract AddressContract, path: :address
    
  # if the check is true, calls the AddressContract on the input at :delivery_address
  check :validate_delivery_address? do
    contract AddressContract, path: :delivery_address
  end

  # these should be inherited, in much the same way as rule macros.
  # a check is different from a rule in that its job is to return true or false, it adds no errors.
  # its purpose is to facilitate application of contracts to some or all of the input, conditional
  # on other parts of the input.
  register_check :validate_delivery_address? do
    # this context has access to values and also options for external dependencies
    values[:delivery_required] && !values[:delivery_address_same]
  end
end  

```

The main new bit of code is the concept of a ResultSet, which quacks like a result, but can composed of Results, optionally mounted at paths (love the Path API btw).

So, next steps?

- [] Decide on reasonable API starting point
- [] Add specs and code to ianwhite/dry-validation for the new behaviour
- [] Go a bit deeper into the desired semantics for ResultSet, and how it should behave in cases of key clashes (ie should we deep merge?, etc)
- [] Document the above changes.

---

<div class="post-metadata">

**Author:** ![i2w](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/i2w/32/30_2.png) [@i2w](https://discourse.dry-rb.org/u/i2w)\
**Post date:** [September 26, 2019, 1:15pm UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/6 "2019-09-26T13:15:03Z")

</div>

Also, FYI, the code currently allows for checks to be lambdas, so that simple cases can just check the existence of keys. Eg.

```ruby
class ExampleContract < ApplicationContract
  contract CustomerContract
  contract AddressContract, path: :address, check: -> { values[:address] }
end

```

---

<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:** [September 27, 2019, 9:42am UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/7 "2019-09-27T09:42:49Z")

</div>

Let’s maybe focus on the contract composition first and deal with the `check` feature separately later. Could you tell me what would be the benefit of having that extra `path` method? Wouldn’t `contract` be enough?

---

<div class="post-metadata">

**Author:** ![i2w](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/i2w/32/30_2.png) [@i2w](https://discourse.dry-rb.org/u/i2w)\
**Post date:** [September 27, 2019, 11:41am UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/8 "2019-09-27T11:41:48Z")

</div>

If you mean the path method vs path option, there is no difference, it’s just syntactic sugar.

Re: the path option TLDR; the benefit is that it will enable re-use and reduce coupling of rules to input paths.

In more detail the benefit is that you will be able to re-use rules in much the same way that you can currently re-use schemas. Simple rules which only have dependencies on the `#key` or `#value` methods can be re-used. But complicated rules which refer to the `#values` method cannot be reused in different nested contexts. By enabling contracts to be ‘mounted’ at different paths on the input, those contracts can be written in a more general way.

```ruby
require 'dry/validation'

# enter two numbers, to guess the rule (their sum must be even)

class EvenContract < Dry::Validation::Contract
  params do
    required(:arg1).filled(:integer)
    required(:arg2).filled(:integer)
  end
  
  rule(:arg1, :arg2) do
    key(:arg2).failure('is not suitable') unless (values[:arg1] + values[:arg2]).even?
  end
end

input = { arg1: "1", arg2: "2" }

EvenContract.new.call(input).errors.to_h # => {:arg2=>["is not suitable"]}

# How would we be able to process this input only using dry-validation, and without re-writing the rules?
guesses_input = { guess1: { arg1: "1", arg2: "2" }, guess2: { arg1: "2", arg2: "4" } }

# With the code spike here https://gist.github.com/ianwhite/a32fcd439020ea07ed1fe3243152274f
# we can do this: (this is ct working code)

guesses_contract = ComposableContract.compose do
  contract EvenContract, path: :guess1
  contract EvenContract, path: :guess2
end

result = guesses_contract.call(guesses_input) #<ComposableContract::ResultSet:0x00007f9fb97a17e0 @success=false, @message_set=nil, @results=[#<ComposableContract::ResultAtPath result=#<Dry::Validation::Result{:arg1=>1, :arg2=>2} errors={:arg2=>["is not suitable"]}> path=[:guess1]>, #<ComposableContract::ResultAtPath result=#<Dry::Validation::Result{:arg1=>2, :arg2=>4} errors={}> path=[:guess2]>], @values=#<Dry::Validation::Values data={:guess1=>{:arg1=>1, :arg2=>2}, :guess2=>{:arg1=>2, :arg2=>4}}>>

result.success? # => false
result.values # => #<Dry::Validation::Values data={:guess1=>{:arg1=>1, :arg2=>2}, :guess2=>{:arg1=>2, :arg2=>4}}>
result.errors # => #<Dry::Validation::MessageSet messages=[#<Dry::Validation::Message text="is not suitable" path=[:guess1, :arg2] meta={}>] options={}>
result.errors.to_h # => {:guess1=>{:arg2=>["is not suitable"]}}

```

This example and a pasteable self contained piece of code is at [https://gist.github.com/ianwhite/918c7ba3487e05b2272a42c154e6b6fb](https://gist.github.com/ianwhite/918c7ba3487e05b2272a42c154e6b6fb)

---

<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:** [September 28, 2019, 10:23am UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/9 "2019-09-28T10:23:21Z")

</div>

> [@i2w](#):
>
> Re: the path option TLDR; the benefit is that it will enable re-use and reduce coupling of rules to input paths.

Right, I got that part, I was just curious if we really need both `path :foo do ... end` and `contract SomeContract, path: :foo`.

> [@i2w](#):
>
> If you mean the path method vs path option, there is no difference, it’s just syntactic sugar.

I guess block-based method would be helpful in drying up cases where you want to apply the same stuff to the same path 😄

> [@i2w](#):
>
> So, next steps?
> 
> - Decide on reasonable API starting point
> - Add specs and code to ianwhite/dry-validation for the new behaviour
> - Go a bit deeper into the desired semantics for ResultSet, and how it should behave in cases of key clashes (ie should we deep merge?, etc)
> - Document the above changes.

I like this plan with the exception of using your fork. If you’re interested in working on this feature I’m more than happy to just give you access to dry-validation repository.

---

<div class="post-metadata">

**Author:** ![i2w](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/i2w/32/30_2.png) [@i2w](https://discourse.dry-rb.org/u/i2w)\
**Post date:** [September 30, 2019, 8:26am UTC](https://discourse.dry-rb.org/t/thoughts-on-dry-validation-wrt-composing-validations/904/10 "2019-09-30T08:26:08Z")

</div>

Hi @solnic, I’m happy to work on this feature. Once I have access, I’ll start a ticket and feature branch there. Thanks! 😄
