dry-schema icon indicating copy to clipboard operation
dry-schema copied to clipboard

Schema with dynamic keys

Open exAspArk opened this issue 8 years ago • 22 comments

Hi,

Are there any options to describe schema with dynamic keys?

Let's say I have a hash where keys are string numbers:

{
  foo: {
    '1' => { name: 'bar1' },
    '2' => { name: 'bar2' },
     ...
    '100' => { name: 'bar100' }
  }
}

For now I can write a schema by using some limited predefined keys:

Dry::Validation.Schema do
  required(:foo).schema do
    ('1'..'100').each do |position|
      optional(position).schema do
        required(:name).filled(:str?)
      end
    end
  end
end

But this approach is not that flexible in case if I need to accept keys which may be any number.

Is using imperative custom validation blocks the only option to write flexible validation in this case?

BarSchema = Dry::Validation.Schema do
  required(:name).filled(:str?)
end

schema = Dry::Validation.Schema do
  required(:foo).filled

  configure do
    def self.messages
      # How can I get detailed errors here from BarSchema?
      super.merge(en: { errors: { valid_foo: 'foo is not valid' } })
    end
  end

  validate(valid_foo: :foo) do |key_values|
    key_values.all? do |k, v|
      k ~= /\A\d+\z/ && BarSchema.call(v).success?
    end
  end
end

exAspArk avatar Jan 03 '17 14:01 exAspArk

Not sure, but maybe allowing to write some custom key matchers would be useful in this case. For example:

REGEX = /\A\d+\z/

Dry::Validation.Schema do
  matcher(:string_number) { |key| key ~= REGEX }

  required(:foo).schema do
    match(:string_number).schema do
      required(:name).filled(:str?)
    end
  end
end

Or something like:

Dry::Validation.Schema do
  required(:foo).schema do
    optional(REGEX).schema do
      required(:name).filled(:str?)
    end
  end
end

exAspArk avatar Jan 03 '17 15:01 exAspArk

This feature is planned for 1.0.0

solnic avatar Jan 09 '17 22:01 solnic

Hi, we are just thinking about using dry-validate in our project. Something custom written is in place right now. We face the same problem and have dynamic keys at some places. Any idea when Version 1.0.0 and this feature will be ready?

marcohelmerich avatar Mar 14 '17 09:03 marcohelmerich

👍 For this feature

ndemianc avatar Sep 07 '17 09:09 ndemianc

Is there any likelihood of this feature being selected for development soon?

jphastings avatar Oct 06 '17 14:10 jphastings

@jphastings we want to support this in 1.0.0, I hope to start working on it next month.

solnic avatar Oct 07 '17 09:10 solnic

@solnic any update on this?

kkbaranski avatar Feb 19 '18 12:02 kkbaranski

@kbredemeier it'll be supported in dry-validation 1.0.0, as mentioned, it'll be done later this year, hopefully before the summer

solnic avatar Feb 19 '18 12:02 solnic

@solnic any news here?

anthonydmitriyev avatar Jan 03 '19 15:01 anthonydmitriyev

@anthonydmitriyev unfortunately, I'm still working on dry-schema. It'll be ready when it's ready. Stay tuned.

solnic avatar Jan 04 '19 12:01 solnic

Any update on this @solnic ?

jacobtani avatar Oct 28 '21 02:10 jacobtani

No update on this front, @jacobtani, nor any immediate plans AFAIK.

You can be sure that we'll update or close this issue when we've done any related work.

timriley avatar Oct 28 '21 08:10 timriley

@solnic it would be fantastic to have a solution for this. Keep running into this again and again when validating fields coming from Rails' nested_attributes, which come in the format:

{ id => { … }, id => { … } }

If there's a temporary work-around or any solution I can help implement, please do let me know.

Thank you!

tomasc avatar Jan 04 '22 17:01 tomasc

@tomasc we'll add it eventually, I'm now focusing on rom/hanami2.0 so I don't have time for dry-validation/schema. It is what it is 🤷🏻

There is no workaround other than to use dry-validation without specifying keys and using custom rules to validate things manually. In order to have this feature done properly, we need a new rule type in dry-logic and then adding support for it here. I wouldn't be surprised if it turns out to be a simple addition at the end of the day 😅

solnic avatar Jan 05 '22 08:01 solnic

@solnic thanks for getting back to me. I see … and understand ;-). Pity though, this seems to be the last piece of the puzzle (at least for me) that would cover all use cases when using dry-validation with Rails / form objects / contracts.

The issue I had specifically was that I use contract schemas with dry-rails. And since the nested Hashes are not being processed by the nested Param schemas, I am running into issues.

My workaround is to pre-process the params manually by looping through the nested hashes, applying the schemas that process the attributes, and re-adding the processed hashes back, before passing them onto the safe_params. Not the most beautiful thing, but gets the work done.

tomasc avatar Jan 05 '22 09:01 tomasc

@tomasc I currently have dry-validation in a rails app at work, so it is very likely that I will need this feature soon too. If this happens, I'll most likely just go ahead and implement it 🤞🏻

solnic avatar Jan 05 '22 10:01 solnic

Hopefully, @solnic doesn't mind me quoting him here, but I asked him a while back what he thought it would take to support this, and this is his answer. I had intended on looking into implementing it myself, but I haven't had time. Maybe someone else does, and this answer is the key to helping them.

I know how to do it and what's required. We need a new operation sub-type in dry-logic that would work like Dry::Logic::Operation::Key but instead of extracting a value found under a specific path (a key is actually a path to a value), it would extract one or more values found under paths matching a regex pattern. Then we'll need to come up with an AST node that would represent a dynamic key and add support for it to the Dry::Logic::RuleCompiler and THEN we'll need to extend schema's DSL to support dynamic key syntax, ie required(/a-z+/).value(:string)

adam12 avatar Jan 19 '22 18:01 adam12

@adam12 I don't mind 🙂 This hasn't changed btw, it's still what needs to be done (more or less)

solnic avatar Jan 20 '22 07:01 solnic

I have keys that are random UUID's. (Basically a JSON object acting as a Dictionary.) Is there even an ugly workaround? Or just SOL since it cannot be defined as part of the AST?

wmakley avatar Sep 12 '22 14:09 wmakley

You can pre-transform input like

transformed = {
  values: input.to_h { |key, value| { key: key, value: value } }
}
Schema = Dry::Schema.JSON do
  required(:values).array(:hash) do
    required(:key).value(:string)
    required(:value).value(...)
  end
end

flash-gordon avatar Sep 13 '22 07:09 flash-gordon

It's enough to check if input is a Hash before, otherwise it's a safe transformation.

flash-gordon avatar Sep 13 '22 07:09 flash-gordon

I've also had this issue when using Rails nested_attributes. My not so elegant solution was to use dry-validation and a macro:

 register_macro(:nested_schema) do |macro:|
    if value.present?
      params = macro.args[0]
      values[key_name].each do |attr_key, attr_value|
        res = params.call(attr_value)
        next if res.success?

        res.errors.each do |m|
          key([key_name, attr_key, m.path&.first]).failure(m.text)
        end
      end
    end
  end

A rule based on the macro:

rule(:foo_attributes).validate(
  nested_schema: Dry::Schema.Params do
    required(:fu).filled(:string)
    required(:bar).filled(:string,
                            included_in?: BAR_CODES)
  end
)

And finaly a schema entry:

optional(:foo_attributes).value(:hash)

hendrixfan avatar Feb 10 '23 19:02 hendrixfan