Search code examples
rubydry-validation

Creating nested/reusable validators in Ruby with dry-validation


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",
    }
}

Solution

  • 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