# Schema composition

**URL:** <https://discourse.dry-rb.org/t/schema-composition/1806>\
**Category:** dry-validation\
**Created:** [June 28, 2024, 7:57pm UTC](https://discourse.dry-rb.org/t/schema-composition/1806 "2024-06-28T19:57:45Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![beechnut](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/beechnut/32/977_2.png) [@beechnut](https://discourse.dry-rb.org/u/beechnut)\
**Post date:** [June 28, 2024, 7:57pm UTC](https://discourse.dry-rb.org/t/schema-composition/1806/1 "2024-06-28T19:57:45Z")

</div>

Hi dry-rb community! Forgive me if I’m asking a repeat question, but I’ve searched issues and forums and tried quite a bit of experimentation, but I’m still not able to solve my issue.

I’m using dry-schema / dry-validation in Hanami, but this question felt more applicable to dry-rb since it’s mostly not about the Hanami framework.

Here’s the summary:

- I have a schema, nested under a top-level key.
- I want to break the schema up into components (e.g. `Name`, `MailingAddress`, so I can use those components separately)
- Then, I want to be able to compose those components back into this schema, nested under the top-level key

I can see [how to compose schemas](https://dry-rb.org/gems/dry-validation/1.10/schemas/#re-using-schemas), and I can see [how to nest](https://dry-rb.org/gems/dry-schema/1.13/nested-data/#nested-code-hash-code) — but I can’t figure out how to do both, and nothing I’ve tried has worked.

Here’s what I’d like to be able to do, where each of the classes is its own contract, with params, rules, and macros.

I think the first question is: **is what I’m trying to do possible?**

Thanks in advance for the help!

```auto
module MySlice
  module Contracts
    module AddressChangeRequest
      class Create < MySlice::Contract
        params do
          required(:change_request).hash(
            Name &
            Email &
            SocialSecurityNumber &
            MailingAddress
          )
        end
      end
    end
  end
end

```

Here’s the current state, which I’d like to break into components for the commented sections:

```ruby
module Address
  module Contracts
    module ChangeRequest
      class Create < Address::Contract
        params do
          required(:change_request).hash do
            # Name
            required(:first_name).filled(:string)
            required(:last_name).filled(:string)
            # Email
            required(:email_address).filled(:string)
            # Social security number
            required(:social_security_number).filled(:string)
            # Mailing address
            required(:address_line_1).filled(:string)
            required(:address_line_2).maybe(:string)
            required(:city).filled(:string)
            required(:state).filled(:string)
            required(:zip).filled(:string)
          end
        end

        # Would be part of the SocialSecurityNumber contract
        # Rules for validating social security number characteristics
        rule(change_request: :social_security_number).validate(:ssn_format)
        rule(change_request: :social_security_number).validate(:ssn_zero_groups)
        rule(change_request: :social_security_number).validate(:ssn_area_range)

        # Would be part of the SocialSecurityNumber contract
        # Definition of rules for validating social security number characteristics
        register_macro(:ssn_format) do
          # omitted for brevity
        end
        register_macro(:ssn_zero_groups) do
          # omitted for brevity
        end
        register_macro(:ssn_area_range) do
          # omitted for brevity
        end

      end
    end
  end
end

```

Valid params for both of these should be structured like:

```ruby
{
  change_request: {
    first_name: "Matt",
    last_name: "Cloyd",
    # ... and so on ...
  }
}

```

---

<div class="post-metadata">

**Author:** ![alassek](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/alassek/32/314_2.png) [@alassek](https://discourse.dry-rb.org/u/alassek)\
**Post date:** [July 3, 2024, 6:39pm UTC](https://discourse.dry-rb.org/t/schema-composition/1806/2 "2024-07-03T18:39:51Z")

</div>

If I were you I’d define a `ChangeRequest` schema and nest it here, but you can also do it inline although it’s pretty janky-looking.

```ruby
class Create < MySlice::Contract
  params do
    required(:change_request).hash(
      Dry::Schema.define(parent: [
        Name,
        Email,
        SocialSecurityNumber,
        MailingAddress
      ])
    )
  end
end

```

---

<div class="post-metadata">

**Author:** ![beechnut](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/beechnut/32/977_2.png) [@beechnut](https://discourse.dry-rb.org/u/beechnut)\
**Post date:** [July 10, 2024, 6:38pm UTC](https://discourse.dry-rb.org/t/schema-composition/1806/3 "2024-07-10T18:38:08Z")

</div>

Thanks for the lead, @alassek!

In the interest of developing my knowledge here: what led you to think of the `parent:` key, and where is the documentation on that? I’ve been poking around dry-schema and dry-validation with little luck.

I’ve been fiddling with this, trying to find something that works — using `:parent` here gave me an error, but I found something that got me close.

```ruby
require 'dry/validation'

class Name < Dry::Validation::Contract
  params do
    required(:first_name).filled(:string)
    required(:last_name).filled(:string)
  end
end

class Email < Dry::Validation::Contract
  params do
    required(:email_address).filled(:string)
  end
end

class CombinedParent < Dry::Validation::Contract
  params do
    required(:change_request).hash(
      Dry::Schema.define(parent: [Name, Email])
    )
  end
end
#=> ArgumentError: Parent configs differ, left=#<Class:0x000000012456d900>::Name, right=#<Class:0x000000012456d900>::Email   
# from /vendor/bundle/ruby/3.3.0/gems/dry-schema-1.13.4/lib/dry/schema/dsl.rb:504:in `block in default_config'

class CombinedSmall < Dry::Validation::Contract
  params do
    required(:change_request).hash(
      Name.schema & Email.schema 
    )
  end
end

CombinedSmall.new.call({
  change_request: { 
    first_name: "a",
    last_name: "z",
    email_address: "e"
  }
}).success?
#=> true

```

The problem I’m running into is, this only works for schemas, not validations, or rules. When I try to incorporate the `SocialSecurityNumber` module into this, which has macros and rules, those rules don’t get executed (because they’re not part of the schema) and I can’t figure out how to include them (calling `SocialSecurityNumber.rules` in the schema definition doesn’t work).

Any thoughts on composing validations here?

---

<div class="post-metadata">

**Author:** ![alassek](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/alassek/32/314_2.png) [@alassek](https://discourse.dry-rb.org/u/alassek)\
**Post date:** [July 10, 2024, 7:30pm UTC](https://discourse.dry-rb.org/t/schema-composition/1806/4 "2024-07-10T19:30:12Z")

</div>

There is no documentation of that that I’m aware of, I know about it because I discover things about these frameworks by looking at the code.

Schemas and Contracts are different animals. You can’t use them interchangeably.

Think about Schemas as context-free rules; they validate a single key-value pair at a time.

Contracts are a layer above Schemas that run validation rules against the whole context.

The reason why your code doesn’t work is that you’re trying to make a schema inherit from a contract. You have to extract the inner schemas first.

```ruby
class CombinedParent < Dry::Validation::Contract
  params do
    required(:change_request).hash(
      Dry::Schema.define(parent: [Name.schema, Email.schema])
    )
  end
end

```

The only officially supported way to make reusable contract rules is through [macros](https://dry-rb.org/gems/dry-validation/1.10/macros/).

There was [some work](https://github.com/dry-rb/dry-validation/issues/593) done to make Contracts composable, but this was not completed. That thread does contain code for a macro that gets you close to what you want.
