Class: ElasticGraph::Support::HashUtil
- Inherits:
-
Object
- Object
- ElasticGraph::Support::HashUtil
- Defined in:
- lib/elastic_graph/support/hash_util.rb
Class Method Summary collapse
-
.deep_merge(hash1, hash2) ⇒ Object
Recursively merges the values from ‘hash2` into `hash1`, without mutating either `hash1` or `hash2`.
-
.disjoint_merge(hash1, hash2) ⇒ Object
Like ‘Hash#merge`, but verifies that the hashes were strictly disjoint (e.g. had no keys in common).
-
.fetch_leaf_values_at_path(hash, key_path, &default) ⇒ Object
Fetches a list of (potentially) nested value from a hash.
-
.fetch_value_at_path(hash, path_parts) ⇒ Object
Fetches a single value from the hash at the given path.
-
.flatten_and_stringify_keys(source_hash, prefix: nil) ⇒ Object
Recursively flattens the provided source hash, converting keys to strings along the way with dots used to separate nested parts.
-
.recursively_prune_nils_and_empties_from(object, &block) ⇒ Object
Recursively prunes nil values or empty hash/array values from the hash, at any level of its structure, without mutating the provided argument.
-
.recursively_prune_nils_from(object, &block) ⇒ Object
Recursively prunes nil values from the hash, at any level of its structure, without mutating the provided argument.
-
.strict_to_h(pairs) ⇒ Object
Like ‘Hash#to_h`, but strict.
-
.stringify_keys(object) ⇒ Object
Recursively transforms any hash keys in the given object to string keys, without mutating the provided argument.
-
.symbolize_keys(object) ⇒ Object
Recursively transforms any hash keys in the given object to symbol keys, without mutating the provided argument.
-
.verbose_fetch(hash, key) ⇒ Object
Fetches a key from a hash (just like ‘Hash#fetch`) but with a more verbose error message when the key is not found.
Class Method Details
.deep_merge(hash1, hash2) ⇒ Object
Recursively merges the values from ‘hash2` into `hash1`, without mutating either `hash1` or `hash2`. When a key is in both `hash2` and `hash1`, takes the value from `hash2` just like `Hash#merge` does.
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/elastic_graph/support/hash_util.rb', line 101 def self.deep_merge(hash1, hash2) # `_ =` needed to satisfy steep--the types here are quite complicated. _ = hash1.merge(hash2) do |key, hash1_value, hash2_value| if ::Hash === hash1_value && ::Hash === hash2_value deep_merge(hash1_value, hash2_value) else hash2_value end end end |
.disjoint_merge(hash1, hash2) ⇒ Object
Like ‘Hash#merge`, but verifies that the hashes were strictly disjoint (e.g. had no keys in common). An error is raised if they do have any keys in common.
36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/elastic_graph/support/hash_util.rb', line 36 def self.disjoint_merge(hash1, hash2) conflicting_keys = [] # : ::Array[untyped] merged = hash1.merge(hash2) do |key, v1, _v2| conflicting_keys << key v1 end unless conflicting_keys.empty? raise ::KeyError, "Hashes were not disjoint. Conflicting keys: #{conflicting_keys.inspect}." end merged end |
.fetch_leaf_values_at_path(hash, key_path, &default) ⇒ Object
Fetches a list of (potentially) nested value from a hash. The ‘key_path` is expected to be an array of path parts. Returns `[]` if the value at any parent key is `nil`. Returns a flat array of values if the structure at any level is an array.
Raises an error if the key is not found unless a default block is provided. Raises an error if any parent value is not a hash as expected. Raises an error if the provided path is not a full path to a leaf in the nested structure.
119 120 121 |
# File 'lib/elastic_graph/support/hash_util.rb', line 119 def self.fetch_leaf_values_at_path(hash, key_path, &default) do_fetch_leaf_values_at_path(hash, key_path, 0, &default) end |
.fetch_value_at_path(hash, path_parts) ⇒ Object
Fetches a single value from the hash at the given path. The ‘path_parts` is expected to be an array.
If any parent value is not a hash as expected, raises an error. If the key at any level is not found, yields to the provided block (which can provide a default value) or raises an error if no block is provided.
Note: this is a somewhat lengthy implementation, but it was chosen based on benchmarking. This method needs to be fast because it gets used repeatedly when resolving GraphQL queries at all levels of the response structure.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/elastic_graph/support/hash_util.rb', line 132 def self.fetch_value_at_path(hash, path_parts) # We expect the most common case to be a single path part. Benchmarks have shown that special casing it # is quite worthwhile, as it is much faster than the general purpose implementation further below. if path_parts.size == 1 return hash.fetch(path_parts.first) do return yield path_parts if block_given? raise KeyError, "Key not found: #{path_parts.inspect}" end end current = hash i = 0 while i < path_parts.length key = path_parts[i] unless current.is_a?(Hash) raise KeyError, "Value at key #{path_parts.first(i).inspect} is not a `Hash` as expected; " \ "instead, was a `#{current.class}`" end current = current.fetch(key) do missing_path = path_parts.first(i + 1) return yield missing_path if block_given? raise KeyError, "Key not found: #{missing_path.inspect}" end i += 1 end current end |
.flatten_and_stringify_keys(source_hash, prefix: nil) ⇒ Object
Recursively flattens the provided source hash, converting keys to strings along the way with dots used to separate nested parts. For example:
flatten_and_stringify_keys({ a: { b: 3 }, c: 5 }, prefix: “foo”) returns: { “foo.a.b” => 3, “foo.c” => 5 }
90 91 92 93 94 95 96 97 |
# File 'lib/elastic_graph/support/hash_util.rb', line 90 def self.flatten_and_stringify_keys(source_hash, prefix: nil) # @type var flat_hash: ::Hash[::String, untyped] flat_hash = {} prefix = prefix ? "#{prefix}." : "" # `_ =` is needed by steep because it thinks `prefix` could be `nil` in spite of the above line. populate_flat_hash(source_hash, _ = prefix, flat_hash) flat_hash end |
.recursively_prune_nils_and_empties_from(object, &block) ⇒ Object
Recursively prunes nil values or empty hash/array values from the hash, at any level of its structure, without mutating the provided argument. Key paths that are pruned are yielded to the caller to allow the caller to have awareness of what was pruned.
75 76 77 78 79 80 81 82 83 |
# File 'lib/elastic_graph/support/hash_util.rb', line 75 def self.recursively_prune_nils_and_empties_from(object, &block) recursively_prune_if(object, block) do |value| if value.is_a?(::Hash) || value.is_a?(::Array) value.empty? else value.nil? end end end |
.recursively_prune_nils_from(object, &block) ⇒ Object
Recursively prunes nil values from the hash, at any level of its structure, without mutating the provided argument. Key paths that are pruned are yielded to the caller to allow the caller to have awareness of what was pruned.
68 69 70 |
# File 'lib/elastic_graph/support/hash_util.rb', line 68 def self.recursively_prune_nils_from(object, &block) recursively_prune_if(object, block, &:nil?) end |
.strict_to_h(pairs) ⇒ Object
Like ‘Hash#to_h`, but strict. When the given input has conflicting keys, `Hash#to_h` will happily let the last pair when. This method instead raises an exception.
23 24 25 26 27 28 29 30 31 32 |
# File 'lib/elastic_graph/support/hash_util.rb', line 23 def self.strict_to_h(pairs) hash = pairs.to_h if hash.size < pairs.size conflicting_keys = pairs.map(&:first).tally.filter_map { |key, count| key if count > 1 } raise ::KeyError, "Cannot build a strict hash, since input has conflicting keys: #{conflicting_keys.inspect}." end hash end |
.stringify_keys(object) ⇒ Object
Recursively transforms any hash keys in the given object to string keys, without mutating the provided argument.
52 53 54 |
# File 'lib/elastic_graph/support/hash_util.rb', line 52 def self.stringify_keys(object) transform_keys(object, :to_s) end |
.symbolize_keys(object) ⇒ Object
Recursively transforms any hash keys in the given object to symbol keys, without mutating the provided argument.
Important note: this should never be used on untrusted input. Symbols are not GCd in Ruby in the same way as strings.
61 62 63 |
# File 'lib/elastic_graph/support/hash_util.rb', line 61 def self.symbolize_keys(object) transform_keys(object, :to_sym) end |
.verbose_fetch(hash, key) ⇒ Object
Fetches a key from a hash (just like ‘Hash#fetch`) but with a more verbose error message when the key is not found. The error message indicates the available keys unlike `Hash#fetch`.
15 16 17 18 19 |
# File 'lib/elastic_graph/support/hash_util.rb', line 15 def self.verbose_fetch(hash, key) hash.fetch(key) do raise ::KeyError, "key not found: #{key.inspect}. Available keys: #{hash.keys.inspect}." end end |