How to skip blanks in nested array?

Hi all!

I have a followup question to How to validate an array property itself and each item of array?. The UI I’m showing is a registration form, where one can type the names of all family members. Some families have more people than others, and I provide more fields than necessary. For example, I show 5 fields for children, when I know most families won’t have 5 children. Same with adults, where I provide 2 fields, but some families will only have one person.

The registration form requires at least one adult, but the family is allowed to have no children.

What I’d like to happen is when the form is submitted, blanks should be skipped: ["", " ", "\t", " "] should evaluate to []. This is my ideal case.

My form definition is as follows.

NewFamilyForm = Dry::Validation.Form do
  required(:email).filled(format?: /.@.+[.][a-z]{1}/i)
  required(:name).filled(min_size?: 1)

  required(:adult).filled(:array?, min_size?: 1).each do
    none? | (str? & min_size?(1))
  end

  required(:child).each do
    none? | (str? & min_size?(1))
  end
end

When I call the form with no adults, I do not receive an error on the adult field, since the empty string is accepted. Here’s a failing test for me:

def test_requires_at_least_one_adult
  result = NewFamilyForm.call("adult" => [""])
  assert_equal ["must be filled"], result.errors[:adult], result.errors.inspect
  assert_equal({adult: []}, result.output)
end

and running it:

  3) Failure:
NewFamilyFormTest#test_requires_at_least_one_adult [test/test_new_family_form.rb:19]:
{:email=>["is missing"], :name=>["is missing"], :child=>["is missing"]}.
Expected: ["must be filled"]
  Actual: nil

Is it possible to ignore blank values during validation? Ideally, #output would not return blank strings, but I can live with those. Should I pre-process the form before attempting form validation?

Thanks!
François

Looks like you want to switch to explicit type specs and define a type that will pre-process the input:

require 'dry-validation'

module Types
  include Dry::Types.module
end

FamilyNames = Types::Array.constructor { |a| a.reject(&:empty?) }

NewFamilyForm = Dry::Validation.Form do
  configure do
    config.type_specs = true
  end

  required(:email, :string).filled(format?: /.@.+[.][a-z]{1}/i)
  required(:name, :string).filled(min_size?: 1)

  required(:adult, FamilyNames).value(:array?, min_size?: 1) { each(:str?, min_size?: 1) }

  required(:child, FamilyNames).each(:str?, min_size?: 1)
end

puts NewFamilyForm.(adult: ['jane', '', 'john']).inspect

Explicit type specs will be very likely the default behavior in 1.0.0 so feel free to use it. We will probably keep type-inference as an optional plugin but with less support from the core team.

Thank you @solnic. I see where explicit types are documented, but I failed to see the road from explicit types to being able to add my own types with a pre-processing step.

Would you like me to extend the example in https://github.com/dry-rb/dry-rb.org/blob/master/source/gems/dry-validation/type-specs.html.md ? I’d be happy to write and submit a PR explaining a pre-processing step. I also believe a link from https://github.com/dry-rb/dry-rb.org/blob/master/source/gems/dry-validation/array-as-input.html.md to type-specs would be good. I looked long and hard at the array as input page.

That would be very helpful :slight_smile:

I have created a PR, as promised: Explain how to do input preprocessing (#129). Enjoy!

1 Like