Sub-nested arrays with schemas


#1

gem: dry-validation
version: 0.10.7

Ok, so I’m trying to validate this structure:

portfolios: [
  {
    name: 'Portfolio 1',
    return_rates: [
      { date: '1998-02', rate: 0.05 },
      { date: '2016-12', rate: 0.012 }
    ]
  },
  {
    name: 'Portfolio 2',
    return_rates: [
      { date: '1998-02', rate: 0.06 },
      { date: '2016-12', rate: 0.04 }
    ]
  }
]

My schema looks like this:

# type_specs = true is enabled for JSON in an initializer

SCHEMA = Dry::Validation.JSON do
  required(:portfolios, :array).filled(min_size?: 1).each do 
    schema(PORTFOLIO_SCHEMA)
  end
end

PORTFOLIO_SCHEMA = Dry::Validation.JSON do
  required(:name, [:nil, :string]).filled(:str?)
  required(:return_rates, :array).filled(min_size?: 1).each do
    schema(PORTFOLIO_RETURN_RATE_SCHEMA)
  end
end

PORTFOLIO_RETURN_RATE_SCHEMA = Dry::Validation.JSON do
  required(:date, [:nil, App::Types::YearMonthDate]).filled(:date?)
  required(:rate, [:nil, :decimal]).filled(:decimal?)
end

This kind of works, the only problem is that if the input is like this:

portfolios: [
  {
    name: 'damn',
    return_rates: []
  }
]

No error is given, it seems like the rule filled(min_size?: 1) is not executed on PORTFOLIO_SCHEMA.
I’ve tried using each as a block in filled(min_size?: 1) { each { schema(PORTFOLIO_SCHEMA) } }, then the validation is executed, but no coercion happens on PORTFOLIO_RETURN_RATE_SCHEMA.

Am I doing something wrong or is this not achievable right now?


#2

Each will not work for empty array. Because each will never be run for empty one. You need extra declaration before to exclude empty. If you don’t want them. Like:

filled?.each ...

#3

@gotar thanks for your answer. But using filled?.each doesn’t work, the schema still passes if I send an empty array of portfolios or return_rates.

I’ve read in other answers that chaining methods is not supported, this might be the cause of it. That’s why I’ve tried to use filled { each }, even value { each } and so on, but none of them work as I needed, this time they do invalidate a schema with an empty array, but then the coercions are not executed, so the valid date in a year-month format inside portfolios are never coerced into Date and so it fails when trying to save the records.

Ok, I just tested something here and it worked, it seems like I need to create a complete coercion rule, like so:

required(
  :portfolios,
  [{ name: :string, return_rates: [{date: App::Types::YearMonthDate, rate: :decimal}] }]
).filled(:array?, min_size?: 1) do
  each do
    schema(PORTFOLIO_SCHEMA)
  end
end

It works, but is quite messy…