Module: Hashly
- Defined in:
- lib/hashly/hashly.rb
Class Method Summary collapse
-
.any?(hash, &block) ⇒ Boolean
Evaluates the block on every key pair recursively.
-
.deep_diff(base, comparison, existing_keys_only: false) ⇒ Object
Deep diff two structures For a hash, returns keys found in both hashes where the values don’t match.
-
.deep_diff_by_key(base, comparison, keep_values: false) ⇒ Object
Identifies what keys in the comparison hash are missing from base hash.
-
.deep_merge(base, override, boolean_or: false, left_outer_join_depth: 0, modify_by_reference: false, selected_overrides: [], excluded_overrides: []) ⇒ Object
Description: Merge two hashes with nested hashes recursively.
-
.deep_reject(hash, &block) ⇒ Object
Reject hash keys however deep they are.
-
.deep_reject_by_hash(base, comparison) ⇒ Object
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.
-
.deep_select(hash, &block) ⇒ Object
Reject hash keys however deep they are.
-
.deep_sort(hash, include_arrays: true) ⇒ Object
Sorts by key recursively - optionally include sorting of arrays.
- .reject_keys_with_nil_values(base) ⇒ Object
- .reject_value?(value, rejected_values) ⇒ Boolean
- .safe_value(hash, *keys) ⇒ Object
- .select_value?(value, selected_values) ⇒ Boolean
- .stringify_all_keys(hash) ⇒ Object
- .symbolize_all_keys(hash) ⇒ Object
Class Method Details
.any?(hash, &block) ⇒ Boolean
Evaluates the block on every key pair recursively. If any block is truthy, the method returns true, otherwise, false.
21 22 23 24 25 26 27 28 29 |
# 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) ⇒ Object
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.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# 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) ⇒ Object
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
90 91 92 93 94 95 96 97 98 99 100 |
# 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: []) ⇒ Object
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.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# 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, &block) ⇒ Object
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.
134 135 136 137 138 139 |
# 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) ⇒ Object
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.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# 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, &block) ⇒ Object
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.
142 143 144 145 146 147 148 149 150 151 152 |
# 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) ⇒ Object
Sorts by key recursively - optionally include sorting of arrays
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# 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) ⇒ Object
174 175 176 |
# 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) ⇒ Boolean
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# 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) ⇒ Object
178 179 180 181 182 |
# 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) ⇒ Boolean
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# 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) ⇒ Object
4 5 6 7 8 9 10 |
# 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) ⇒ Object
12 13 14 15 16 17 18 |
# 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 |