Search code examples
arraysrubyhashyamlblock

Ruby Array of Hash parsing


I have a yaml file in the format:

parameters:
  - param_name: age
    requires:
    - name
  - param_name: height
    requires:
    - name

Based on this format I would like to accept a hash of keys and values and determine if the combination of keys and values is valid. For example based on the above example if someone submitted a hash with the values:

{'age' => 15, 'height' => '6ft'}

it would be considered invalid since the parameter name is required. So a valid submission would look like

{'age' => 15, 'height' => '6ft', 'name' => 'Abe Lincoln'}.

Essentially what I want is this:

For each parameter object, if it has a requires array underneath it. Check all parameter param_names for elements in that array, if any are missing exit.

I have a very ugly double loop that checks for this but I want to tighten the code up. I think I can use blocks in order to validate the data I need. Here is what I have come up with so far:

require 'yaml'
requirements = YAML.load_file('./require.yaml')
require_fields = Array.new

requirements['parameters'].each do |param|
  require_fields.concat(param['require']) if param.has_key? 'require'
end

require_fields.each do |requirement|
  found = false
  requirements['parameters'].each do |param|
    if param['param_name'] == requirement
      found = true
    end
  end
  abort "#{requirement} is a required field" unless found
end

Solution

  • You can clean this up a lot if you make it more idiomatic Ruby:

    require 'yaml'
    requirements = YAML.load_file('./require.yaml')
    
    require_fields = requirements['parameters'].select do |param|
      param.has_key?('require')
    end.map do |param|
      param['require']
    end
    
    require_fields.each do |requirement|
      found = requirements['parameters'].any? do |param|
        param['param_name'] == requirement
      end
    
      abort "#{requirement} is a required field" unless found
    end
    

    You could also do this:

    require_fields = requirements['parameters'].map do |param|
      param['require']
    end.compact
    

    Where that's probably good enough so long as your require is either something or nil.

    You could also transform that input YAML into a simple hash structure of dependencies:

    dependencies = requirements.map do ||
      [ param['param_name'], param['requires'] ]
    end.to_h
    

    Then you can test really easily:

    dependencies.each do |name, requirements|
      found = requirements.find do |required_name|
        !dependencies[required_name]
      end
    
      abort "#{found} is a required field" unless found
    end
    

    This is a really rough adaptation of your code, but I hope it gives you some ideas.