0

Let's say I want to set up a validation contract for addresses, but then I also want to set up a validator for users, and for coffee shops; both of which include an address, is it possible to re-use the AddressContract in UserContract and CoffeeShopContract?

For example, the data I want to validate might look like:

# Address
{
    "first_line": "100 Main street",
    "zipcode": "12345",
}

# User
{
    "first_name": "Joe",
    "last_name": "Bloggs",
    "address:" {
        "first_line": "123 Boulevard",
        "zipcode": "12346",
    }
}

# Coffee Shop
{
    "shop": "Central Perk",
    "floor_space": "2000sqm",
    "address:" {
        "first_line": "126 Boulevard",
        "zipcode": "12347",
    }
}
aidan
  • 9,310
  • 8
  • 68
  • 82

1 Answers1

1

Yes you can reuse schemas (See: Reusing Schemas)

It would look something like this:

require 'dry/validation'
class AddressContract < Dry::Validation::Contract 
  params do 
    required(:first_line).value(:string) 
    required(:zipcode).value(:string) 
  end
end

class UserContract < Dry::Validation::Contract 
  params do
    required(:first_name).value(:string)
    required(:last_name).value(:string)
    required(:address).schema(AddressContract.schema)
  end
end 


a = {first_line: '123 Street Rd'}
u = {first_name: 'engineers', last_name: 'mnky', address: a }

AddressContract.new.(a)
#=> #<Dry::Validation::Result{:first_line=>"123 Street Rd"} errors={:zipcode=>["is missing"]}>
UserContract.new.(u)
#=> #<Dry::Validation::Result{:first_name=>"engineers", :last_name=>"mnky", :address=>{:first_line=>"123 Street Rd"}} errors={:address=>{:zipcode=>["is missing"]}}>

Alternatively you can create schema mixins as well e.g.

AddressSchema = Dry::Schema.Params do 
  required(:first_line).value(:string) 
  required(:zipcode).value(:string) 
end 

class AddressContract < Dry::Validation::Contract 
    params(AddressSchema) 
end

class UserContract < Dry::Validation::Contract 
  params do
    required(:first_name).value(:string)
    required(:last_name).value(:string)
    required(:address).schema(AddressSchema)
  end
end 
engineersmnky
  • 25,495
  • 2
  • 36
  • 52
  • Thanks for the response. I found the part on reusing schemas, but what about the validation? Is there a way to reuse that too? – aidan Mar 30 '21 at 01:58
  • @aidan reusing the schema does reuse the validation. Look at the output I posted for UserContract. It shows `errors={:address=>{:zipcode=>["is missing"]}}` because we are validating the `address` param with the `AddressContract` schema. – engineersmnky Mar 30 '21 at 15:08
  • 1
    What I mean is, if I add a rule to the `AddressContract`, e.g. `rule(:zipcode) { key.failure('zipcode must be 5 digits') unless /^\d{5}$/ === value }` then add an invalid zipcode to the testcase, `AddressContract.new.(a)` will fail but `UserContract.new.(u)` will succeed. It seems `params` are part of the schema, but `rules` are not? – aidan Mar 31 '21 at 06:42
  • @aidan I looked around the code base and there does not appear to be a way to handle this at the moment (without some very hacky solutions). It appears that there is an open ticket for just this purpose [Composable Contracts](https://github.com/dry-rb/dry-validation/issues/593) – engineersmnky Mar 31 '21 at 17:22
  • Thanks so much for you help. I wasn't sure if I was just missing something really obvious and I was feeling pretty lost. – aidan Apr 01 '21 at 23:19