Extending a validation schema

I’m working on switching a project from ActiveRecord validations to dry-validations. Some of the ActiveRecord models are in a Rails engine and re-opened in a Rails application to add additional validations. In the short term I’d like to mimic this.

# in the engine
class Form < Dry::Struct
  def schema
    @schema ||= Dry::Validation.Params do
      required(:short_title).filled
    end
  end
end

In essence I want to take the original schema and extend it.

My guess is that it that the schema object is frozen so it isn’t directly possible. So my first idea was composition:

# in the app, re-opening the class and adding more validations
class Form
  def schema 
    @schema ||= Dry::Validation.Params do
      schema { super } # merge original validations
      required(:email).filled # add new validation
    end
  end
end

However I’m monkey patching so I can’t call super. Even so it does not seem to work:

v1 = Dry::Validation.Params { required(:name).filled }
v2 = Dry::Validation.Params { schema { v1 } }
v2.call(name: nil).errors # => {}

Any pointers/thought would be appreciated.

I can get some of the way with:

class V1 < Dry::Validation::Schema
  define! { required(:short_title).filled }
end

V2 = Dry::Validation.Params(V1) { required(:email).filled }

But that means I have to define the schema in two different ways.

And also using inheritance:

class V1 < Dry::Validation::Schema
  define! { required(:short_title).filled }
end

class V2 < V1
  define! { required(:email).filled }
end

There are several ways to inherit schema without necessarily writing the class.

The key to the first one is the build option in the Dry::Validation.Schema method:

S1 = Dry::Validation.Schema(build: false) do
  required(:name).filled
end

S2 = Dry::Validation.Params(S1) do
  required(:age).filled(:int?, gt?: 0)
end

Be warned, however, that S1 in this case would be the class of the schema, not the schema itself. So instead of doing this:

S1.({name: 'William'})

You will have to do this:

S1.new.({name: 'William'})

The second option is just use the parent schema’s class in the child schema:

S1 = Dry::Validation.Schema do
  required(:name).filled
end

S2 = Dry::Validation.Params(S1.class) do
  required(:age).filled(:int?, gt?: 0)
end
1 Like

Is there a way to extend more than one schema?