Module: Transproc::HashTransformations

Extended by:
Registry
Defined in:
lib/transproc/hash.rb

Overview

Transformation functions for Hash objects

Examples:

require 'transproc/hash'

include Transproc::Helper

fn = t(:symbolize_keys) >> t(:nest, :address, [:street, :zipcode])

fn["street" => "Street 1", "zipcode" => "123"]
# => {:address => {:street => "Street 1", :zipcode => "123"}}

Class Method Summary collapse

Methods included from Registry

[], fetch, import, store

Class Method Details

.accept_keys(hash, keys) ⇒ Hash

Accepts specified keys from a hash

Examples:

Transproc(:accept_keys, [:name])[name: 'Jane', email: '[email protected]']
# => {:name=>"Jane"}

Parameters:

  • hash (Hash)

    The input hash

  • keys (Array)

    The keys to be accepted

Returns:

  • (Hash)

245
246
247
# File 'lib/transproc/hash.rb', line 245

def self.accept_keys(hash, keys)
  accept_keys!(Hash[hash], keys)
end

.accept_keys!(hash, keys) ⇒ Object

Same as `:accept_keys` but mutates the hash

See Also:

  • HashTransformations.accept

254
255
256
# File 'lib/transproc/hash.rb', line 254

def self.accept_keys!(hash, keys)
  reject_keys!(hash, hash.keys - keys)
end

.copy_keys(hash, mapping) ⇒ Hash

Copy all keys in a hash using provided mapping hash

Examples:

Transproc(:copy_keys, user_name: :name)[user_name: 'Jane']
# => {:user_name => "Jane", :name => "Jane"}

Parameters:

  • hash (Hash)

    The input hash

  • mapping (Hash)

    The key-copy mapping

Returns:

  • (Hash)

190
191
192
# File 'lib/transproc/hash.rb', line 190

def self.copy_keys(hash, mapping)
  copy_keys!(Hash[hash], mapping)
end

.copy_keys!(hash, mapping) ⇒ Object

Same as `:copy_keys` but mutates the hash

See Also:


199
200
201
202
203
204
205
206
# File 'lib/transproc/hash.rb', line 199

def self.copy_keys!(hash, mapping)
  mapping.each do |original_key, new_keys|
    [*new_keys].each do |new_key|
      hash[new_key] = hash[original_key]
    end
  end
  hash
end

.deep_merge(hash, other) ⇒ Hash

Merge a hash recursively

Examples:


input = { 'foo' => 'bar', 'baz' => { 'one' => 1 } }
other = { 'foo' => 'buz', 'baz' => { :one => 'one', :two => 2 } }

t(:deep_merge)[input, other]
# => { 'foo' => "buz", :baz => { :one => 'one', 'one' => 1, :two => 2 } }

Parameters:

  • (Hash)
  • (Hash)

Returns:

  • (Hash)

478
479
480
481
482
483
484
485
486
487
# File 'lib/transproc/hash.rb', line 478

def self.deep_merge(hash, other)
  Hash[hash].merge(other) do |_, original_value, new_value|
    if original_value.respond_to?(:to_hash) &&
       new_value.respond_to?(:to_hash)
      deep_merge(Hash[original_value], Hash[new_value])
    else
      new_value
    end
  end
end

.deep_symbolize_keys(hash) ⇒ Hash

Symbolize keys in a hash recursively

Examples:


input = { 'foo' => 'bar', 'baz' => [{ 'one' => 1 }] }

t(:deep_symbolize_keys)[input]
# => { :foo => "bar", :baz => [{ :one => 1 }] }

Parameters:

  • (Hash)

Returns:

  • (Hash)

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/transproc/hash.rb', line 83

def self.deep_symbolize_keys(hash)
  hash.each_with_object({}) do |(key, value), output|
    output[key.to_sym] =
      case value
      when Hash
        deep_symbolize_keys(value)
      when Array
        value.map { |item|
          item.is_a?(Hash) ? deep_symbolize_keys(item) : item
        }
      else
        value
      end
  end
end

.eval_values(hash, args, filters = []) ⇒ Object

Recursively evaluate hash values if they are procs/lambdas

Examples:

hash = {
  num: -> i { i + 1 },
  str: -> i { "num #{i}" }
}

t(:eval_values, 1)[hash]
# => {:num => 2, :str => "num 1" }

# with filters
t(:eval_values, 1, [:str])[hash]
# => {:num => #{still a proc}, :str => "num 1" }

Parameters:

  • (Hash)
  • args (Array, Object)

    Anything that should be passed to procs

  • filters (Array) (defaults to: [])

    A list of attribute names that should be evaluated


440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/transproc/hash.rb', line 440

def self.eval_values(hash, args, filters = [])
  hash.each_with_object({}) do |(key, value), output|
    output[key] =
      case value
      when Proc
        if filters.empty? || filters.include?(key)
          value.call(*args)
        else
          value
        end
      when Hash
        eval_values(value, args, filters)
      when Array
        value.map { |item|
          item.is_a?(Hash) ? eval_values(item, args, filters) : item
        }
      else
        value
      end
  end
end

.fold(hash, key, tuple_key) ⇒ Hash

Folds array of tuples to array of values from a specified key

Examples:

source = {
  name: "Jane",
  tasks: [{ title: "be nice", priority: 1 }, { title: "sleep well" }]
}
Transproc(:fold, :tasks, :title)[source]
# => { name: "Jane", tasks: ["be nice", "sleep well"] }
Transproc(:fold, :tasks, :priority)[source]
# => { name: "Jane", tasks: [1, nil] }

Parameters:

  • hash (Hash)
  • key (Object)

    The key to fold values to

  • tuple_key (Object)

    The key to take folded values from

Returns:

  • (Hash)

366
367
368
# File 'lib/transproc/hash.rb', line 366

def self.fold(hash, key, tuple_key)
  fold!(Hash[hash], key, tuple_key)
end

.fold!(hash, key, tuple_key) ⇒ Object

Same as `:fold` but mutates the hash

See Also:


375
376
377
# File 'lib/transproc/hash.rb', line 375

def self.fold!(hash, key, tuple_key)
  hash.update(key => ArrayTransformations.extract_key(hash[key], tuple_key))
end

.map_keys(hash, fn) ⇒ Hash

Map all keys in a hash with the provided transformation function

Examples:

Transproc(:map_keys, -> s { s.upcase })['name' => 'Jane']
# => {"NAME" => "Jane"}

Parameters:

  • (Hash)

Returns:

  • (Hash)

31
32
33
# File 'lib/transproc/hash.rb', line 31

def self.map_keys(hash, fn)
  map_keys!(Hash[hash], fn)
end

.map_keys!(hash, fn) ⇒ Object

Same as `:map_keys` but mutates the hash

See Also:


40
41
42
43
# File 'lib/transproc/hash.rb', line 40

def self.map_keys!(hash, fn)
  hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
  hash
end

.map_value(hash, key, fn) ⇒ Hash

Map a key in a hash with the provided transformation function

Examples:

Transproc(:map_value, 'name', -> s { s.upcase })['name' => 'jane']
# => {"name" => "JANE"}

Parameters:

  • (Hash)

Returns:

  • (Hash)

269
270
271
# File 'lib/transproc/hash.rb', line 269

def self.map_value(hash, key, fn)
  hash.merge(key => fn[hash[key]])
end

.map_value!(hash, key, fn) ⇒ Object

Same as `:map_value` but mutates the hash

See Also:


278
279
280
# File 'lib/transproc/hash.rb', line 278

def self.map_value!(hash, key, fn)
  hash.update(key => fn[hash[key]])
end

.map_values(hash, fn) ⇒ Hash

Map all values in a hash using transformation function

Examples:

Transproc(:map_values, -> v { v.upcase })[:name => 'Jane']
# => {"name" => "JANE"}

Parameters:

  • (Hash)

Returns:

  • (Hash)

134
135
136
# File 'lib/transproc/hash.rb', line 134

def self.map_values(hash, fn)
  map_values!(Hash[hash], fn)
end

.map_values!(hash, fn) ⇒ Hash

Same as `:map_values` but mutates the hash

Parameters:

  • (Hash)

Returns:

  • (Hash)

See Also:


147
148
149
150
# File 'lib/transproc/hash.rb', line 147

def self.map_values!(hash, fn)
  hash.each { |key, value| hash[key] = fn[value] }
  hash
end

.nest(hash, key, keys) ⇒ Hash

Nest values from specified keys under a new key

Examples:

Transproc(:nest, :address, [:street, :zipcode])[street: 'Street', zipcode: '123']
# => {address: {street: "Street", zipcode: "123"}}

Parameters:

  • (Hash)

Returns:

  • (Hash)

293
294
295
# File 'lib/transproc/hash.rb', line 293

def self.nest(hash, key, keys)
  nest!(Hash[hash], key, keys)
end

.nest!(hash, root, keys) ⇒ Object

Same as `:nest` but mutates the hash

See Also:


302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/transproc/hash.rb', line 302

def self.nest!(hash, root, keys)
  nest_keys = hash.keys & keys

  if nest_keys.size > 0
    child = Hash[nest_keys.zip(nest_keys.map { |key| hash.delete(key) })]
    old_nest = hash[root]
    new_nest = old_nest.is_a?(Hash) ? old_nest.merge(child) : child
    hash.update(root => new_nest)
  else
    hash.update(root => {})
  end
end

.reject_keys(hash, keys) ⇒ Hash

Rejects specified keys from a hash

Examples:

Transproc(:reject_keys, [:name])[name: 'Jane', email: '[email protected]']
# => {:email => "[email protected]"}

Parameters:

  • hash (Hash)

    The input hash

  • keys (Array)

    The keys to be rejected

Returns:

  • (Hash)

220
221
222
# File 'lib/transproc/hash.rb', line 220

def self.reject_keys(hash, keys)
  reject_keys!(Hash[hash], keys)
end

.reject_keys!(hash, keys) ⇒ Object

Same as `:reject_keys` but mutates the hash

See Also:


229
230
231
# File 'lib/transproc/hash.rb', line 229

def self.reject_keys!(hash, keys)
  hash.reject { |k, _| keys.include?(k) }
end

.rename_keys(hash, mapping) ⇒ Hash

Rename all keys in a hash using provided mapping hash

Examples:

Transproc(:rename_keys, user_name: :name)[user_name: 'Jane']
# => {:name => "Jane"}

Parameters:

  • hash (Hash)

    The input hash

  • mapping (Hash)

    The key-rename mapping

Returns:

  • (Hash)

164
165
166
# File 'lib/transproc/hash.rb', line 164

def self.rename_keys(hash, mapping)
  rename_keys!(Hash[hash], mapping)
end

.rename_keys!(hash, mapping) ⇒ Object

Same as `:rename_keys` but mutates the hash

See Also:


173
174
175
176
# File 'lib/transproc/hash.rb', line 173

def self.rename_keys!(hash, mapping)
  mapping.each { |k, v| hash[v] = hash.delete(k) if hash.has_key?(k) }
  hash
end

.split(hash, key, keys) ⇒ Array<Hash>

Splits hash to array by all values from a specified key

The operation adds missing keys extracted from the array to regularize the output.

Examples:

input = {
  name: 'Joe',
  tasks: [
    { title: 'sleep well', priority: 1 },
    { title: 'be nice',    priority: 2 },
    {                      priority: 2 },
    { title: 'be cool'                 }
  ]
}
Transproc(:split, :tasks, [:priority])[input]
=> [
    { name: 'Joe', priority: 1,   tasks: [{ title: 'sleep well' }]              },
    { name: 'Joe', priority: 2,   tasks: [{ title: 'be nice' }, { title: nil }] },
    { name: 'Joe', priority: nil, tasks: [{ title: 'be cool' }]                 }
  ]

Parameters:

  • hash (Hash)
  • key (Object)

    The key to split a hash by

  • subkeys (Array)

    The list of subkeys to be extracted from key

Returns:

  • (Array<Hash>)

407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/transproc/hash.rb', line 407

def self.split(hash, key, keys)
  list = Array(hash[key])
  return [hash.reject { |k, _| k == key }] if list.empty?

  existing  = list.flat_map(&:keys).uniq
  grouped   = existing - keys
  ungrouped = existing & keys

  list = ArrayTransformations.group(list, key, grouped) if grouped.any?
  list = list.map { |item| item.merge(reject_keys(hash, [key])) }
  ArrayTransformations.add_keys(list, ungrouped)
end

.stringify_keys(hash) ⇒ Hash

Stringify all keys in a hash

Examples:

Transproc(:stringify_keys)[:name => 'Jane']
# => {"name" => "Jane"}

Parameters:

  • (Hash)

Returns:

  • (Hash)

110
111
112
# File 'lib/transproc/hash.rb', line 110

def self.stringify_keys(hash)
  stringify_keys!(Hash[hash])
end

.stringify_keys!(hash) ⇒ Object

Same as `:stringify_keys` but mutates the hash

See Also:


119
120
121
# File 'lib/transproc/hash.rb', line 119

def self.stringify_keys!(hash)
  map_keys!(hash, Coercions[:to_string].fn)
end

.symbolize_keys(hash) ⇒ Hash

Symbolize all keys in a hash

Examples:

Transproc(:symbolize_keys)['name' => 'Jane']
# => {:name => "Jane"}

Parameters:

  • (Hash)

Returns:

  • (Hash)

56
57
58
# File 'lib/transproc/hash.rb', line 56

def self.symbolize_keys(hash)
  symbolize_keys!(Hash[hash])
end

.symbolize_keys!(hash) ⇒ Object

Same as `:symbolize_keys` but mutates the hash

See Also:


65
66
67
# File 'lib/transproc/hash.rb', line 65

def self.symbolize_keys!(hash)
  map_keys!(hash, Coercions[:to_symbol].fn)
end

.unwrap(hash, root, keys = nil) ⇒ Hash

Collapse a nested hash from a specified key

Examples:

Transproc(:unwrap, :address, [:street, :zipcode])[address: { street: 'Street', zipcode: '123' }]
# => {street: "Street", zipcode: "123"}

Parameters:

  • (Hash)

Returns:

  • (Hash)

326
327
328
329
# File 'lib/transproc/hash.rb', line 326

def self.unwrap(hash, root, keys = nil)
  copy = Hash[hash].merge(root => Hash[hash[root]])
  unwrap!(copy, root, keys)
end

.unwrap!(hash, root, selected = nil) ⇒ Object

Same as `:unwrap` but mutates the hash

See Also:


336
337
338
339
340
341
342
343
344
345
# File 'lib/transproc/hash.rb', line 336

def self.unwrap!(hash, root, selected = nil)
  if nested_hash = hash[root]
    keys = nested_hash.keys
    keys &= selected if selected
    hash.update(Hash[keys.zip(keys.map { |key| nested_hash.delete(key) })])
    hash.delete(root) if nested_hash.empty?
  end

  hash
end