module Nuggets::Hash::UnrollMixin

Public Instance Methods

unroll(*value_keys) → anArray click to toggle source
unroll(*value_keys, sort_by: ...) → anArray
unroll(*value_keys) { |value_hash| ... } → anArray

“Unrolls” a nested hash, so that each path through hash results in a row that is, e.g., suitable for use with CSV.

Note that from the final hash (“value hash”) only the values are used, namely, if value_keys are given, the values at those keys are returned. If a block is given, the value_hash is passed to that block for any additional processing or sanitization.

If sort_by is given, all hashes are passed through that block for sorting before being put into the result array.

Examples:

{ foo: { bar: { a: { x: 1, y: 2 }, b: { x: 0, y: 3 } } } }.unroll
#=> [[:foo, :bar, :b, 3, 0], [:foo, :bar, :a, 2, 1]]

{ foo: { bar: { a: { x: 1, y: 2 }, b: { x: 0, y: 3 } } } }.unroll(sort_by: :to_s)
#=> [[:foo, :bar, :a, 1, 2], [:foo, :bar, :b, 0, 3]]

{ foo: { bar: { a: { x: 1, y: 2 }, b: { x: 0, y: 3 } } } }.unroll { |data| data[:x] = nil; data[:y] *= 2 }
#=> [[:foo, :bar, :b, 6, nil], [:foo, :bar, :a, 4, nil]]
# File lib/nuggets/hash/unroll_mixin.rb, line 57
def unroll(*value_keys, &block)
  args = value_keys.dup
  options = value_keys.last.is_a?(::Hash) ? value_keys.pop : {}

  sort_proc = if options.key?(:sort_by)
    lambda { sort_by(&options[:sort_by]) }
  elsif options.key?(:sort)
    options[:sort] == true ? lambda { sort } : lambda { sort(&options[:sort]) }
  end

  rows = []

  if values.first.is_a?(self.class)  # if any is, then all are
    (sort_proc ? sort_proc.call : self).each { |key, value|
      value.unroll(*args, &block).each { |row| rows << [key, *row] }
    }
  else
    block[self] if block

    rows << if value_keys.empty?
      sort_proc ? sort_proc.call.map { |key, value| value } : values
    else
      values_at(*value_keys)
    end
  end

  rows
end