How can I convert a string into an array of hashes?

I’m using dry-validations 0.13.3 with Ruby 2.6.2. I’d like to convert a string passed in from our frontend into an array of hashes:

schema = Dry::Validation.Schema do
  required(:sorts).each do
    schema do
      required(:name).value(included_in?: %i[a b])
      required(:dir).value(included_in?: %i[asc desc])
    end
  end
end

puts schema.call(sorts: 'a:asc,b:desc').inspect

This yields the error:

#<Dry::Validation::Result output={:sorts=>"a:asc,b:desc"} errors={:sorts=>["must be an array"]}>

Following the instructions about input preprocessing, I tried creating a custom type:

SortString =
  Dry::Types::Definition
    .new(Array)
    .constructor do |input|
  input.split(',').map do |s|
    name, dir = s.split(':', 2)
    { name: name.to_sym, dir: dir.to_sym }
  end
end

puts SortString['a:asc,b:desc']

This appears to work:

{:name=>:a, :dir=>:asc}
{:name=>:b, :dir=>:desc}

But using it in the validation has the same error:

schema = Dry::Validation.Schema do
  configure { config.type_specs = true }

  required(:sorts, SortString).each do
    schema do
      required(:name).value(included_in?: %i[a b])
      required(:dir).value(included_in?: %i[asc desc])
    end
  end
end

puts schema.call(sorts: 'a:asc,b:desc').inspect
#<Dry::Validation::Result output={:sorts=>"a:asc,b:desc"} errors={:sorts=>["must be an array"]}>

For the record, no idea if it’s possible in 0.x series but dry-schema 1.x can do this:

SortString = Types::Array.constructor do |input|
  input.split(',').map do |s|
    name, dir = s.split(':', 2)
    { name: name.to_sym, dir: dir.to_sym }
  end
end

Schema = Dry::Schema.define do
  required(:sorts).value(SortString).each do
    hash do
      required(:name).value(included_in?: %i[a b])
      required(:dir).value(included_in?: %i[asc desc])
    end
  end
end

dry-schema> Schema.(sorts: 'a:asc,b:desc')
=> #<Dry::Schema::Result{:sorts=>[{:name=>:a, :dir=>:asc}, {:name=>:b, :dir=>:desc}]} errors={}>

I’m not entirely sure what the difference is, but I did get it to work:

# frozen_string_literal: true

require 'dry-validation'

SortString =
  Dry::Types::Definition
    .new(Array)
    .constructor do |input|
  input.split(',').map do |s|
    by, dir = s.split(':', 2)
    { by: by.to_sym, dir: dir.to_sym }
  end
end


VALID_SORTS = %i[a b].freeze
VALID_SORT_DIRECTIONS = %i[asc desc].freeze

RefinementsContract = Dry::Validation.Params do
  configure do
    config.type_specs = true
  end

  optional(:sorts, SortString).each do
    schema do
      required(:by).value(included_in?: VALID_SORTS)
      required(:dir).value(included_in?: VALID_SORT_DIRECTIONS)
    end
  end
end

puts RefinementsContract.call(sorts: 'a:asc,b:desc').inspect

Is there some mapping that needs to be done to transform your example using dry-schema directly to be used in dry-validations? I ran this code:

# frozen_string_literal: true

require 'dry-validation'

module Types
  include Dry.Types()
end

SortString = Types.Constructor(Array) do |values|
  values.split(',').map do |s|
    by, dir = s.split(':', 2)
    { by: by.to_sym, dir: dir.to_sym }
  end
end

p SortString['name:asc,date:desc']

class ExampleContract < Dry::Validation::Contract
  VALID_SORTS = %i[name date].freeze
  VALID_SORT_DIRECTIONS = %i[asc desc].freeze

  params do
    optional(:sorts).value(SortString).each do
      hash do
        required(:by).value(included_in?: VALID_SORTS)
        required(:dir).value(included_in?: VALID_SORT_DIRECTIONS)
      end
    end
  end
end

p ExampleContract.new.call('sorts' => 'name:asc,date:desc')

But it fails to validate:

[{:by=>:name, :dir=>:asc}, {:by=>:date, :dir=>:desc}]
#<Dry::Validation::Result{:sorts=>"name:asc,date:desc"} errors={:sorts=>["must be an array"]}>```
  • dry-validation (1.1.1)
  • dry-schema (1.2.0)

There’s apparently an important difference between what you used:

SortString = Types::Array.constructor do |input|

And how it is documented to make a custom type:

SortString = Types.Constructor(Array) do |values|
  1. Where is the former documented?
  2. What is the meaningful difference in the two?
  3. Is this a bug that should be filed?

I think so. But it’s not clear where. I’ll take another look.

Thank you. I’ve opened

ok, thanks, though it’s 100% not dry-validation :slight_smile: It’s either dry-schema or dry-types. Also Types.Constructor(Array) and Types::Array.constructor are different types but in this context I would expect them to work the same way.