module Hashly

Public Instance Methods

any?(hash) { |k, v| ... } click to toggle source

Evaluates the block on every key pair recursively. If any block is truthy, the method returns true, otherwise, false.

# File lib/hashly/hashly.rb, line 21
def any?(hash, &block)
  raise 'hash is a required argument' if hash.nil?
  raise 'A block must be provided to this method to evaluate on each key pair. The evaluation occurs recursively. Block arguments: |k, v|' if block.nil?
  hash.each do |k, v|
    return true if yield(k, v)
    return true if v.is_a?(::Hash) && any?(v, &block) # recurse
  end
  false
end
deep_diff(base, comparison, existing_keys_only: false) click to toggle source

Deep diff two structures For a hash, returns keys found in both hashes where the values don't match. If a key exists in the base, but NOT the comparison, it is NOT considered a difference so that it can be a one way comparison. For an array, returns an array with values found in the comparison array but not in the base array.

# File lib/hashly/hashly.rb, line 106
def deep_diff(base, comparison, existing_keys_only: false)
  if base.nil? # if base is nil, entire comparison object is different
    return {} if comparison.nil?
    return comparison.is_a?(Hash) ? comparison.dup : comparison
  end

  case comparison
  when nil
    {}
  when ::Hash
    differing_values = {}
    base = base.dup
    comparison.each do |src_key, src_value|
      next if existing_keys_only && base.is_a?(::Hash) && !base.keys.include?(src_key)
      difference = deep_diff(base[src_key], src_value, existing_keys_only: existing_keys_only)
      differing_values[src_key] = difference unless difference == :no_diff
    end
    differing_values.reject { |_k, v| v.is_a?(::Hash) && v.empty? }
  when ::Array
    return comparison unless base.is_a?(::Array)
    difference = comparison - base
    difference.empty? ? :no_diff : difference
  else
    base == comparison ? :no_diff : comparison
  end
end
deep_diff_by_key(base, comparison, keep_values: false) click to toggle source

Identifies what keys in the comparison hash are missing from base hash. Optionally keep the values from the comparison hash, otherwise assigns the missing keys a value of :missing_key

# File lib/hashly/hashly.rb, line 90
def deep_diff_by_key(base, comparison, keep_values: false)
  missing_keys = {}
  if comparison.is_a?(::Hash)
    compared_keys = base.is_a?(::Hash) ? comparison.keys - base.keys : comparison.keys # Determine what keys the comparison has that the base doesn't
    compared_keys.each { |k| missing_keys[k] = keep_values ? comparison[k] : :missing_key } # Save the missing keys
    comparison.each do |k, v|
      missing_keys[k] = deep_diff_by_key(base[k], v, keep_values: keep_values) if v.is_a?(::Hash) # Recurse to find more missing keys if the hash goes deeper
    end
  end
  missing_keys.reject { |_k, v| v.is_a?(::Hash) && v.empty? } # Remove any empty hashes as there were no missing keys in them
end
deep_merge(base, override, boolean_or: false, left_outer_join_depth: 0, modify_by_reference: false, selected_overrides: [], excluded_overrides: []) click to toggle source

Description:

Merge two hashes with nested hashes recursively.

Returns:

Hash with the merged data.

Parameters:

boolean_or: use a boolean || operator on the base and override if they are not a Hash or Array instead of stomping with the override.
left_outer_join_depth: Only merge keys that already exist in the base for the first X levels specified.
modify_by_reference: Hashes will be modified by reference which will modify the actual parameters.
selected_overrides: Allows for specifying a regex or value(s) that should ONLY be merged into the base Hash.
excluded_overrides: Allows for specifying a regex or value(s) that should NOT be merged into the base Hash.
# File lib/hashly/hashly.rb, line 56
def deep_merge(base, override, boolean_or: false, left_outer_join_depth: 0, modify_by_reference: false, selected_overrides: [], excluded_overrides: [])
  left_outer_join_depth -= 1 # decrement left_outer_join_depth for recursion
  return base if reject_value?(override, excluded_overrides)
  return base unless select_value?(override, selected_overrides)
  if base.nil?
    return nil if left_outer_join_depth >= 0
    return modify_by_reference || !override.is_a?(::Hash) ? override : override.dup
  end

  case override
  when nil
    base = base.dup unless modify_by_reference || !base.is_a?(::Hash) # duplicate hash to avoid modification by reference issues
    base # if override doesn't exist, simply return the existing value
  when ::Hash
    return override unless base.is_a?(::Hash)
    base = base.dup unless modify_by_reference || !base.is_a?(::Hash)
    override.each do |src_key, src_value|
      next if base[src_key].nil? && left_outer_join_depth >= 0 # if this is a left outer join and the key does not exist in the base, skip it
      base[src_key] = base[src_key] ? deep_merge(base[src_key], src_value, boolean_or: boolean_or, left_outer_join_depth: left_outer_join_depth, selected_overrides: selected_overrides, excluded_overrides: excluded_overrides) : src_value # Recurse if both are Hash
    end
    base
  when ::Array
    return override unless base.is_a?(::Array)
    base |= override
    base
  when ::String, ::Integer, ::Time, ::TrueClass, ::FalseClass, ::Symbol
    boolean_or ? base || override : override
  else
    throw "Implementation for deep merge of type #{override.class} is missing."
  end
end
deep_reject(hash) { |k, v| ... } click to toggle source

Reject hash keys however deep they are. Provide a block and if it evaluates to true for a given key/value pair, it will be rejected.

# File lib/hashly/hashly.rb, line 134
def deep_reject(hash, &block)
  hash.each_with_object({}) do |(k, v), h|
    next if yield(k, v) # reject the current key/value pair by skipping it if the block given evaluates to true
    h[k] = v.is_a?(::Hash) ? deep_reject(v, &block) : v # recursively go up the hash tree or keep the value if it's not a hash.
  end
end
deep_reject_by_hash(base, comparison) click to toggle source

Deep diff two Hashes Remove any keys in the first hash also contained in the second hash If a key exists in the base, but NOT the comparison, it is kept.

# File lib/hashly/hashly.rb, line 157
def deep_reject_by_hash(base, comparison)
  return nil if base.nil?

  case comparison
  when ::Hash
    return base unless base.is_a?(::Hash) # if base is not a hash but the comparison is, return the base
    base = base.dup
    comparison.each do |src_key, src_value|
      base[src_key] = deep_reject_by_hash(base[src_key], src_value) # recurse to the leaf
      base[src_key] = nil if base[src_key].is_a?(::Hash) && base[src_key].empty? # set leaves to nil if they are empty hashes
    end
    base.reject { |_k, v| v.nil? } # reject any leaves that were set to nil
  else # rubocop:disable Style/EmptyElse - for clarity
    nil # drop the value if we have reached a leaf in the comparison hash
  end
end
deep_select(hash) { |k, v| ... } click to toggle source

Reject hash keys however deep they are. Provide a block and if it evaluates to true for a given key/value pair, it will be rejected.

# File lib/hashly/hashly.rb, line 142
def deep_select(hash, &block)
  hash.each_with_object({}) do |(k, v), h|
    if v.is_a?(::Hash)
      h[k] = deep_select(v, &block)
      h.delete(k) if h[k].is_a?(::Hash) && h[k].empty?
      next
    end
    next unless yield(k, v) # skip the current key/value pair unless the block given evaluates to true
    h[k] = v
  end
end
deep_sort(hash, include_arrays: true) click to toggle source

Sorts by key recursively - optionally include sorting of arrays

# File lib/hashly/hashly.rb, line 32
def deep_sort(hash, include_arrays: true)
  raise "argument must be of type Hash - Actual type: #{hash.class}" unless hash.is_a?(::Hash)
  hash.each_with_object({}) do |(k, v), child_hash|
    child_hash[k] = case v
                    when ::Hash
                      deep_sort(v)
                    when ::Array
                      include_arrays ? v.sort : v
                    else
                      v
                    end
  end.sort.to_h
end
reject_keys_with_nil_values(base) click to toggle source
# File lib/hashly/hashly.rb, line 174
def reject_keys_with_nil_values(base)
  deep_reject(base) { |_k, v| v.nil? }
end
reject_value?(value, rejected_values) click to toggle source
# File lib/hashly/hashly.rb, line 184
def reject_value?(value, rejected_values)
  return false if rejected_values == :disabled

  rejected_values = [rejected_values] if rejected_values.is_a?(String) || !rejected_values.is_a?(::Array)
  rejected_values.each do |rejected_value|
    case rejected_value
    when Regexp
      next if value.is_a?(::Hash) || value.is_a?(::Array) # Don't evaluate regexp on Hash or Array
      return true if (value.to_s =~ rejected_value) == 0
    else
      return true if value == rejected_value
    end
  end
  false
end
safe_value(hash, *keys) click to toggle source
# File lib/hashly/hashly.rb, line 178
def safe_value(hash, *keys)
  return nil if hash.nil? || hash[keys.first].nil?
  return hash[keys.first] if keys.length == 1 # return the value if we have reached the final key
  safe_value(hash[keys.shift], *keys) # recurse until we have reached the final key
end
select_value?(value, selected_values) click to toggle source
# File lib/hashly/hashly.rb, line 200
def select_value?(value, selected_values)
  return true if selected_values == :disabled || selected_values.nil? || selected_values.empty?

  selected_values = [selected_values] if selected_values.is_a?(String) || !selected_values.is_a?(::Array)
  selected_values.each do |selected_value|
    case selected_value
    when Regexp
      next if value.is_a?(::Hash) || value.is_a?(::Array) # Don't evaluate regexp on Hash or Array
      return true if (value.to_s =~ selected_value) == 0
    else
      return true if value == selected_value
    end
  end
  false
end
stringify_all_keys(hash) click to toggle source
# File lib/hashly/hashly.rb, line 4
def stringify_all_keys(hash)
  stringified_hash = {}
  hash.each do |k, v|
    stringified_hash[k.to_s] = v.is_a?(::Hash) ? stringify_all_keys(v) : v
  end
  stringified_hash
end
symbolize_all_keys(hash) click to toggle source
# File lib/hashly/hashly.rb, line 12
def symbolize_all_keys(hash)
  symbolized_hash = {}
  hash.each do |k, v|
    symbolized_hash[k.to_sym] = v.is_a?(::Hash) ? symbolize_all_keys(v) : v
  end
  symbolized_hash
end