Class: Hash

Inherits:
Object
  • Object
show all
Includes:
Everythingrb::InspectQuotable
Defined in:
lib/everythingrb/hash.rb

Overview

Extensions to Ruby’s core Hash class

Provides:

  • #to_struct, #to_ostruct, #to_istruct: Convert hashes to different structures

  • #join_map: Combine filter_map and join operations

  • #transform_values.with_key: Transform values with access to keys

  • #transform, #transform!: Transform keys and values

  • #value_where, #values_where: Find values based on conditions

  • #rename_key, #rename_keys: Rename hash keys while preserving order

  • ::new_nested_hash: Create automatically nesting hashes

  • #merge_if, #merge_if!: Conditionally merge based on key-value pairs

  • #merge_if_values, #merge_if_values!: Conditionally merge based on values

  • #merge_compact, #merge_compact!: Merge only non-nil values

Examples:

require "everythingrb/hash"
config = {server: {port: 443}}.to_ostruct
config.server.port  # => 443

Constant Summary collapse

EMPTY_STRUCT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

A minimal empty struct for Ruby 3.2+ compatibility

Ruby 3.2 enforces stricter argument handling for Struct. This means trying to create a Struct from an empty Hash will result in an ArgumentError being raised. This is trying to keep a consistent experience with that version and newer versions.

Returns:

  • (Struct)

    A struct with a single nil-valued field

Struct.new(:_).new(nil)

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Everythingrb::InspectQuotable

#in_quotes

Class Method Details

.new_nested_hash(depth: nil) ⇒ Hash

Note:

This implementation is not thread-safe for concurrent modifications of deeply nested structures. If you need thread safety, consider using a mutex when modifying the deeper levels of the hash.

Note:

While unlimited nesting is convenient, it can interfere with common Ruby patterns like ||= when initializing values at deep depths. Use the depth parameter to control this behavior.

Creates a new Hash that automatically initializes missing keys with nested hashes

This method creates a hash where any missing key access will automatically create another nested hash with the same behavior. You can control the nesting depth with the depth parameter.

Examples:

Unlimited nesting (default behavior)

users = Hash.new_nested_hash
users[:john][:role] = "admin"  # No need to initialize users[:john] first
users # => {john: {role: "admin"}}

Deep nesting without initialization

stats = Hash.new_nested_hash
stats[:server][:region][:us_east][:errors] = ["Error"]
stats # => {server: {region: {us_east: {errors: ["Error"]}}}}

Limited nesting depth

hash = Hash.new_nested_hash(depth: 1)
hash[:user][:name] = "Alice"  # Works fine - only one level of auto-creation

# This pattern works correctly with limited nesting:
(hash[:user][:roles] ||= []) << "admin"
hash # => {user: {name: "Alice", roles: ["admin"]}}

Parameters:

  • depth (Integer, nil) (defaults to: nil)

    The maximum nesting depth for automatic hash creation When nil (default), creates unlimited nesting depth When 0, behaves like a regular hash (returns nil for missing keys) When > 0, automatically creates hashes only up to the specified level

Returns:

  • (Hash)

    A hash that creates nested hashes for missing keys



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/everythingrb/hash.rb', line 79

def self.new_nested_hash(depth: nil)
  new do |hash, key|
    next if depth == 0

    hash[key] =
      if depth.nil?
        new_nested_hash
      else
        new_nested_hash(depth: depth - 1)
      end
  end
end

Instance Method Details

#deep_transform_values(with_key: false) {|value, key| ... } ⇒ Hash, Enumerator

Recursively transforms all values in the hash and nested structures

Walks through the hash and all nested hashes/arrays and yields each non-hash and non-array value to the block, replacing it with the block’s return value.

Examples:

Basic usage

hash = {a: 1, b: {c: 2, d: [3, 4]}}
hash.deep_transform_values { |v| v * 2 }
# => {a: 2, b: {c: 4, d: [6, 8]}}

With key access

hash = {a: 1, b: {c: 2}}
hash.deep_transform_values(with_key: true) { |v, k| "#{k}:#{v}" }
# => {a: "a:1", b: {c: "c:2"}}

Parameters:

  • with_key (Boolean) (defaults to: false)

    Whether to yield both value and key to the block

Yields:

  • (value, key)

    Block that transforms each value

Yield Parameters:

  • value (Object)

    The value to transform

  • key (Object)

    The corresponding key (only if with_key: true)

Yield Returns:

  • (Object)

    The transformed value

Returns:

  • (Hash)

    A new hash with all values recursively transformed

  • (Enumerator)

    If no block is given



358
359
360
361
362
363
364
365
366
# File 'lib/everythingrb/hash.rb', line 358

def deep_transform_values(with_key: false, &block)
  return to_enum(:deep_transform_values, with_key:) if block.nil?

  if with_key
    _deep_transform_values_with_key(self, nil, &block)
  else
    og_deep_transform_values(&block)
  end
end

#deep_transform_values!(with_key: false) {|value, key| ... } ⇒ self, Enumerator

Recursively transforms all values in the hash and nested structures in place

Same as #deep_transform_values but modifies the hash in place.

Examples:

Basic usage

hash = {a: 1, b: {c: 2, d: [3, 4]}}
hash.deep_transform_values! { |v| v * 2 }
# => {a: 2, b: {c: 4, d: [6, 8]}}
# hash is now {a: 2, b: {c: 4, d: [6, 8]}}

With key access

hash = {a: 1, b: {c: 2}}
hash.deep_transform_values!(with_key: true) { |v, k| "#{k}:#{v}" }
# => {a: "a:1", b: {c: "c:2"}}
# hash is now {a: "a:1", b: {c: "c:2"}}

Parameters:

  • with_key (Boolean) (defaults to: false)

    Whether to yield both value and key to the block

Yields:

  • (value, key)

    Block that transforms each value

Yield Parameters:

  • value (Object)

    The value to transform

  • key (Object)

    The corresponding key (only if with_key: true)

Yield Returns:

  • (Object)

    The transformed value

Returns:

  • (self)

    The transformed hash

  • (Enumerator)

    If no block is given



395
396
397
398
399
400
401
402
403
# File 'lib/everythingrb/hash.rb', line 395

def deep_transform_values!(with_key: false, &block)
  return to_enum(:deep_transform_values!, with_key:) if block.nil?

  if with_key
    _deep_transform_values_with_key!(self, nil, &block)
  else
    og_deep_transform_values!(&block)
  end
end

#join_map(join_with = "") {|key, value| ... } ⇒ String

Combines filter_map and join operations

Examples:

{ a: 1, b: nil, c: 2, d: nil, e: 3 }.join_map(", ") { |k, v| "#{k}-#{v}" if v }
# => "a-1, c-2, e-3"

Without a block

{ a: 1, b: nil, c: 2 }.join_map(" ")
# => "a 1 b c 2"

Parameters:

  • join_with (String) (defaults to: "")

    The delimiter to join elements with (defaults to empty string)

Yields:

  • (key, value)

    Block that filters and transforms hash entries

Yield Parameters:

  • key (Object)

    The current key

  • value (Object)

    The current value

Returns:

  • (String)

    Joined string of filtered and transformed entries



111
112
113
114
115
# File 'lib/everythingrb/hash.rb', line 111

def join_map(join_with = "", &block)
  block = ->(kv_pair) { kv_pair.compact } if block.nil?

  filter_map(&block).join(join_with)
end

#merge_compact(other = {}) ⇒ Hash

Merges only non-nil values from another hash

This is a convenience method for the common pattern of merging only values that are not nil.

Examples:

Merge only non-nil values (common when building parameters)

user_id = 42
email = nil
name = "Alice"

{}.merge_compact(
  id: user_id,
  email: email,
  name: name
)
# => {id: 42, name: "Alice"}

Parameters:

  • other (Hash) (defaults to: {})

    The hash to merge from

Returns:

  • (Hash)

    A new hash with non-nil values merged



863
864
865
# File 'lib/everythingrb/hash.rb', line 863

def merge_compact(other = {})
  merge_if_values(other, &:itself)
end

#merge_compact!(other = {}) ⇒ self

Merges only non-nil values from another hash, in place

This is a convenience method for the common pattern of merging only values that are not nil.

Examples:

Merge only non-nil values in place

params = {format: "json"}
params.merge_compact!(
  page: 1,
  per_page: nil,
  sort: "created_at"
)
# => {format: "json", page: 1, sort: "created_at"}

Parameters:

  • other (Hash) (defaults to: {})

    The hash to merge from

Returns:

  • (self)

    The modified hash



886
887
888
# File 'lib/everythingrb/hash.rb', line 886

def merge_compact!(other = {})
  merge_if_values!(other, &:itself)
end

#merge_if(other = {}) {|key, value| ... } ⇒ Hash

Conditionally merges key-value pairs from another hash based on a block

Examples:

Merge only even-numbered keys

{a: 1, b: 2}.merge_if(c: 3, d: 4) { |key, _| key.to_s.ord.even? }
# => {a: 1, b: 2, d: 4}

Merge only positive values

{a: 1, b: 2}.merge_if(c: 3, d: -4) { |_, value| value > 0 }
# => {a: 1, b: 2, c: 3}

Parameters:

  • other (Hash) (defaults to: {})

    The hash to merge from

Yields:

  • (key, value)

    Block that determines whether to include each key-value pair

Yield Parameters:

  • key (Object)

    The key from the other hash

  • value (Object)

    The value from the other hash

Yield Returns:

  • (Boolean)

    Whether to include this key-value pair

Returns:

  • (Hash)

    A new hash with conditionally merged key-value pairs



773
774
775
776
777
# File 'lib/everythingrb/hash.rb', line 773

def merge_if(other = {}, &block)
  other = other.select(&block) unless block.nil?

  merge(other)
end

#merge_if!(other = {}) {|key, value| ... } ⇒ self

Conditionally merges key-value pairs from another hash in place

Examples:

Merge only even-numbered keys in place

hash = {a: 1, b: 2}
hash.merge_if!(c: 3, d: 4) { |key, _| key.to_s.ord.even? }
# => {a: 1, b: 2, d: 4}

Parameters:

  • other (Hash) (defaults to: {})

    The hash to merge from

Yields:

  • (key, value)

    Block that determines whether to include each key-value pair

Yield Parameters:

  • key (Object)

    The key from the other hash

  • value (Object)

    The value from the other hash

Yield Returns:

  • (Boolean)

    Whether to include this key-value pair

Returns:

  • (self)

    The modified hash



796
797
798
799
800
# File 'lib/everythingrb/hash.rb', line 796

def merge_if!(other = {}, &block)
  other = other.select(&block) unless block.nil?

  merge!(other)
end

#merge_if_values(other = {}) {|value| ... } ⇒ Hash

Conditionally merges key-value pairs based only on values

Examples:

Merge only string values

{a: 1, b: "old"}.merge_if_values(c: "new", d: 2) { |v| v.is_a?(String) }
# => {a: 1, b: "old", c: "new"}

Parameters:

  • other (Hash) (defaults to: {})

    The hash to merge from

Yields:

  • (value)

    Block that determines whether to include each value

Yield Parameters:

  • value (Object)

    The value from the other hash

Yield Returns:

  • (Boolean)

    Whether to include this value

Returns:

  • (Hash)

    A new hash with conditionally merged values



817
818
819
# File 'lib/everythingrb/hash.rb', line 817

def merge_if_values(other = {}, &block)
  merge_if(other) { |k, v| block.call(v) }
end

#merge_if_values!(other = {}) {|value| ... } ⇒ self

Conditionally merges key-value pairs based only on values, in place

Examples:

Merge only numeric values in place

hash = {a: 1, b: "text"}
hash.merge_if_values!(c: "ignore", d: 2) { |v| v.is_a?(Numeric) }
# => {a: 1, b: "text", d: 2}

Parameters:

  • other (Hash) (defaults to: {})

    The hash to merge from

Yields:

  • (value)

    Block that determines whether to include each value

Yield Parameters:

  • value (Object)

    The value from the other hash

Yield Returns:

  • (Boolean)

    Whether to include this value

Returns:

  • (self)

    The modified hash



837
838
839
# File 'lib/everythingrb/hash.rb', line 837

def merge_if_values!(other = {}, &block)
  merge_if!(other) { |k, v| block.call(v) }
end

#reject_values {|value| ... } ⇒ Hash, Enumerator

Rejects hash entries based only on their values

Examples:

Remove blank values (with ActiveSupport)

{name: "Alice", bio: nil, role: ""}.reject_values(&:blank?)
# => {name: "Alice"}

Remove specific types of values

{id: 1, count: 0, items: [1, 2, 3]}.reject_values { |v| v.is_a?(Integer) && v == 0 }
# => {id: 1, items: [1, 2, 3]}

Yields:

  • (value)

    Block that determines whether to exclude the entry

Yield Parameters:

  • value (Object)

    The current value

Yield Returns:

  • (Boolean)

    Whether to exclude this entry

Returns:

  • (Hash)

    A new hash excluding entries where the block returned truthy

  • (Enumerator)

    If no block is given



725
726
727
728
729
# File 'lib/everythingrb/hash.rb', line 725

def reject_values(&block)
  return to_enum(:reject_values) if block.nil?

  reject { |_k, v| block.call(v) }
end

#reject_values! {|value| ... } ⇒ self, ...

Rejects hash entries based only on their values, modifying the hash in place

Examples:

Remove blank values in place (with ActiveSupport)

hash = {name: "Alice", bio: nil, role: ""}
hash.reject_values!(&:blank?)
# => {name: "Alice"}
# hash is now {name: "Alice"}

Yields:

  • (value)

    Block that determines whether to remove the entry

Yield Parameters:

  • value (Object)

    The current value

Yield Returns:

  • (Boolean)

    Whether to remove this entry

Returns:

  • (self, nil)

    The modified hash, or nil if no changes were made

  • (Enumerator)

    If no block is given



747
748
749
750
751
# File 'lib/everythingrb/hash.rb', line 747

def reject_values!(&block)
  return to_enum(:reject_values!) if block.nil?

  reject! { |_k, v| block.call(v) }
end

#rename_key(old_key, new_key) ⇒ Hash

Renames a key in the hash while preserving the original order of elements

Examples:

Renames a single key

{a: 1, b: 2, c: 3}.rename_key(:b, :middle)
# => {a: 1, middle: 2, c: 3}

Parameters:

  • old_key (Object)

    The key to rename

  • new_key (Object)

    The new key to use

Returns:

  • (Hash)

    A new hash with the key renamed



548
549
550
# File 'lib/everythingrb/hash.rb', line 548

def rename_key(old_key, new_key)
  rename_keys(old_key => new_key)
end

#rename_key!(old_key, new_key) ⇒ self

Renames a key in the hash in place while preserving the original order of elements

Examples:

Renames a key in place

hash = {a: 1, b: 2, c: 3}
hash.rename_key!(:b, :middle)
# => {a: 1, middle: 2, c: 3}

Parameters:

  • old_key (Object)

    The key to rename

  • new_key (Object)

    The new key to use

Returns:

  • (self)

    The modified hash



565
566
567
# File 'lib/everythingrb/hash.rb', line 565

def rename_key!(old_key, new_key)
  rename_keys!(old_key => new_key)
end

#rename_key_unordered(old_key, new_key) ⇒ Hash

Renames a key in the hash without preserving element order (faster)

This method is significantly faster than #rename_key but does not guarantee that the order of elements in the hash will be preserved.

Examples:

Rename a single key without preserving order

{a: 1, b: 2, c: 3}.rename_key_unordered(:b, :middle)
# => {a: 1, c: 3, middle: 2}  # Order may differ

Parameters:

  • old_key (Object)

    The key to rename

  • new_key (Object)

    The new key to use

Returns:

  • (Hash)

    A new hash with the key renamed



627
628
629
630
631
632
633
634
# File 'lib/everythingrb/hash.rb', line 627

def rename_key_unordered(old_key, new_key)
  # Fun thing I learned. For small hashes, using #except is 1.5x faster than using dup and delete.
  # But as the hash becomes larger, the performance improvements become diminished until they're roughly the same.
  # Neat!
  hash = except(old_key)
  hash[new_key] = self[old_key]
  hash
end

#rename_key_unordered!(old_key, new_key) ⇒ self

Renames a key in the hash in place without preserving element order (faster)

This method is significantly faster than #rename_key! but does not guarantee that the order of elements in the hash will be preserved.

Examples:

Rename a key in place without preserving order

hash = {a: 1, b: 2, c: 3}
hash.rename_key_unordered!(:b, :middle)
# => {a: 1, c: 3, middle: 2}  # Order may differ

Parameters:

  • old_key (Object)

    The key to rename

  • new_key (Object)

    The new key to use

Returns:

  • (self)

    The modified hash



652
653
654
655
# File 'lib/everythingrb/hash.rb', line 652

def rename_key_unordered!(old_key, new_key)
  self[new_key] = delete(old_key)
  self
end

#rename_keys(**keys) ⇒ Hash

Renames multiple keys in the hash while preserving the original order of elements

This method maintains the original order of all keys in the hash, renaming only the specified keys while keeping their positions unchanged.

Examples:

Renames multiple keys

{a: 1, b: 2, c: 3, d: 4}.rename_keys(a: :first, c: :third)
# => {first: 1, b: 2, third: 3, d: 4}

Parameters:

  • keys (Hash)

    A mapping of old_key => new_key pairs

Returns:

  • (Hash)

    A new hash with keys renamed



583
584
585
586
587
588
# File 'lib/everythingrb/hash.rb', line 583

def rename_keys(**keys)
  # I tried multiple different ways to rename the key while preserving the order, this was the fastest
  transform_keys do |key|
    keys.key?(key) ? keys[key] : key
  end
end

#rename_keys!(**keys) ⇒ self

Renames multiple keys in the hash in place while preserving the original order of elements

This method maintains the original order of all keys in the hash, renaming only the specified keys while keeping their positions unchanged.

Examples:

Rename multiple keys in place

hash = {a: 1, b: 2, c: 3, d: 4}
hash.rename_keys!(a: :first, c: :third)
# => {first: 1, b: 2, third: 3, d: 4}

Parameters:

  • keys (Hash)

    A mapping of old_key => new_key pairs

Returns:

  • (self)

    The modified hash



605
606
607
608
609
610
# File 'lib/everythingrb/hash.rb', line 605

def rename_keys!(**keys)
  # I tried multiple different ways to rename the key while preserving the order, this was the fastest
  transform_keys! do |key|
    keys.key?(key) ? keys[key] : key
  end
end

#select_values {|value| ... } ⇒ Hash, Enumerator Also known as: filter_values

Selects hash entries based only on their values

Examples:

Filter to include only present values (with ActiveSupport)

{name: "Alice", bio: nil, role: ""}.select_values(&:present?)
# => {name: "Alice"}

Filter using more complex logic

{id: 1, count: 0, items: [1, 2, 3]}.select_values { |v| v.is_a?(Array) || v > 0 }
# => {id: 1, items: [1, 2, 3]}

Yields:

  • (value)

    Block that determines whether to include the entry

Yield Parameters:

  • value (Object)

    The current value

Yield Returns:

  • (Boolean)

    Whether to include this entry

Returns:

  • (Hash)

    A new hash including only entries where the block returned truthy

  • (Enumerator)

    If no block is given



675
676
677
678
679
# File 'lib/everythingrb/hash.rb', line 675

def select_values(&block)
  return to_enum(:select_values) if block.nil?

  select { |_k, v| block.call(v) }
end

#select_values! {|value| ... } ⇒ self, ... Also known as: filter_values!

Selects hash entries based only on their values, modifying the hash in place

Examples:

Remove entries with empty values (with ActiveSupport)

hash = {name: "Alice", bio: nil, role: ""}
hash.select_values!(&:present?)
# => {name: "Alice"}
# hash is now {name: "Alice"}

Yields:

  • (value)

    Block that determines whether to keep the entry

Yield Parameters:

  • value (Object)

    The current value

Yield Returns:

  • (Boolean)

    Whether to keep this entry

Returns:

  • (self, nil)

    The modified hash, or nil if no changes were made

  • (Enumerator)

    If no block is given



699
700
701
702
703
# File 'lib/everythingrb/hash.rb', line 699

def select_values!(&block)
  return to_enum(:select_values!) if block.nil?

  select! { |_k, v| block.call(v) }
end

#to_deep_hHash

Recursively converts all values that respond to #to_h

Similar to #to_h but recursively traverses the Hash structure and calls #to_h on any object that responds to it. Useful for normalizing nested data structures and parsing nested JSON.

Examples:

Converting nested Data objects

user = { name: "Alice", metadata: Data.define(:source).new(source: "API") }
user.to_deep_h  # => {name: "Alice", metadata: {source: "API"}}

Parsing nested JSON strings

nested = { profile: '{"role":"admin"}' }
nested.to_deep_h  # => {profile: {role: "admin"}}

Mixed nested structures

data = {
  config: OpenStruct.new(api_key: "secret"),
  users: [
    Data.define(:name).new(name: "Bob"),
    {role: "admin"}
  ]
}
data.to_deep_h
# => {
#      config: {api_key: "secret"},
#      users: [{name: "Bob"}, {role: "admin"}]
#    }

Returns:

  • (Hash)

    A deeply converted hash with all nested objects



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/everythingrb/hash.rb', line 148

def to_deep_h
  transform_values do |value|
    case value
    when Hash
      value.to_deep_h
    when Array
      value.to_deep_h
    when String
      # If the string is not valid JSON, #to_deep_h will return `nil`
      value.to_deep_h || value
    else
      value.respond_to?(:to_h) ? value.to_h : value
    end
  end
end

#to_istructData

Converts hash to an immutable Data structure

Examples:

hash = { person: { name: "Bob", age: 30 } }
data = hash.to_istruct
data.person.name # => "Bob"
data.class # => Data

Returns:

  • (Data)

    An immutable Data object with the same structure



175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/everythingrb/hash.rb', line 175

def to_istruct
  recurse = lambda do |input|
    case input
    when Hash
      input.to_istruct
    when Array
      input.map(&recurse)
    else
      input
    end
  end

  Data.define(*keys.map(&:to_sym)).new(*values.map { |value| recurse.call(value) })
end

#to_ostructOpenStruct

Converts hash to an OpenStruct recursively

Examples:

hash = { config: { api_key: "secret" } }
config = hash.to_ostruct
config.config.api_key # => "secret"

Returns:

  • (OpenStruct)

    An OpenStruct with methods matching hash keys



229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/everythingrb/hash.rb', line 229

def to_ostruct
  recurse = lambda do |value|
    case value
    when Hash
      value.to_ostruct
    when Array
      value.map(&recurse)
    else
      value
    end
  end

  OpenStruct.new(**transform_values { |value| recurse.call(value) })
end

#to_structStruct

Converts hash to a Struct recursively

Examples:

hash = { user: { name: "Alice", roles: ["admin"] } }
struct = hash.to_struct
struct.user.name # => "Alice"
struct.class # => Struct

Returns:

  • (Struct)

    A struct with methods matching hash keys



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/everythingrb/hash.rb', line 201

def to_struct
  # For Ruby 3.2, it raises if you attempt to create a Struct with no keys
  return EMPTY_STRUCT if RUBY_VERSION.start_with?("3.2") && empty?

  recurse = lambda do |value|
    case value
    when Hash
      value.to_struct
    when Array
      value.map(&recurse)
    else
      value
    end
  end

  Struct.new(*keys.map(&:to_sym)).new(*values.map { |value| recurse.call(value) })
end

#transform {|key, value| ... } ⇒ Hash, Enumerator

Transforms keys and values to create a new hash

Examples:

Transform both keys and values

{a: 1, b: 2}.transform { |k, v| ["#{k}_key", v * 2] }
# => {a_key: 2, b_key: 4}

Yields:

  • (key, value)

    Block that returns a new key-value pair

Yield Parameters:

  • key (Object)

    The original key

  • value (Object)

    The original value

Yield Returns:

  • (Array)

    Two-element array containing the new key and value

Returns:

  • (Hash)

    A new hash with transformed keys and values

  • (Enumerator)

    If no block is given



457
458
459
460
461
# File 'lib/everythingrb/hash.rb', line 457

def transform(&block)
  return to_enum(:transform) if block.nil?

  to_h(&block)
end

#transform! {|key, value| ... } ⇒ self, Enumerator

Transforms keys and values in place

Examples:

Transform both keys and values in place

hash = {a: 1, b: 2}
hash.transform! { |k, v| ["#{k}_key", v * 2] }
# => {a_key: 2, b_key: 4}

Yields:

  • (key, value)

    Block that returns a new key-value pair

Yield Parameters:

  • key (Object)

    The original key

  • value (Object)

    The original value

Yield Returns:

  • (Array)

    Two-element array containing the new key and value

Returns:

  • (self)

    The transformed hash

  • (Enumerator)

    If no block is given



479
480
481
482
483
# File 'lib/everythingrb/hash.rb', line 479

def transform!(&block)
  return to_enum(:transform!) if block.nil?

  replace(transform(&block))
end

#transform_values(with_key: false) {|value, key| ... } ⇒ Hash, Enumerator

Returns a new hash with all values transformed by the block

Enhances Ruby’s standard transform_values with key access capability.

Examples:

Standard usage

{a: 1, b: 2}.transform_values { |v| v * 2 }
# => {a: 2, b: 4}

With key access

{a: 1, b: 2}.transform_values(with_key: true) { |v, k| "#{k}:#{v}" }
# => {a: "a:1", b: "b:2"}

Parameters:

  • with_key (Boolean) (defaults to: false)

    Whether to yield both value and key to the block

Yields:

  • (value, key)

    Block that transforms each value

Yield Parameters:

  • value (Object)

    The value to transform

  • key (Object)

    The corresponding key (only if with_key: true)

Yield Returns:

  • (Object)

    The transformed value

Returns:

  • (Hash)

    A new hash with transformed values

  • (Enumerator)

    If no block is given



273
274
275
276
277
278
279
280
281
282
283
# File 'lib/everythingrb/hash.rb', line 273

def transform_values(with_key: false, &block)
  return to_enum(:transform_values, with_key:) if block.nil?

  if with_key
    each_pair.with_object({}) do |(key, value), output|
      output[key] = block.call(value, key)
    end
  else
    og_transform_values(&block)
  end
end

#transform_values!(with_key: false) {|value, key| ... } ⇒ self, Enumerator

Transforms all values in the hash in place

Enhances Ruby’s standard transform_values! with key access capability.

Examples:

Standard usage

hash = {a: 1, b: 2}
hash.transform_values! { |v| v * 2 }
# => {a: 2, b: 4}
# hash is now {a: 2, b: 4}

With key access

hash = {a: 1, b: 2}
hash.transform_values!(with_key: true) { |v, k| "#{k}:#{v}" }
# => {a: "a:1", b: "b:2"}
# hash is now {a: "a:1", b: "b:2"}

Parameters:

  • with_key (Boolean) (defaults to: false)

    Whether to yield both value and key to the block

Yields:

  • (value, key)

    Block that transforms each value

Yield Parameters:

  • value (Object)

    The value to transform

  • key (Object)

    The corresponding key (only if with_key: true)

Yield Returns:

  • (Object)

    The transformed value

Returns:

  • (self)

    The transformed hash

  • (Enumerator)

    If no block is given



312
313
314
315
316
317
318
319
320
321
322
# File 'lib/everythingrb/hash.rb', line 312

def transform_values!(with_key: false, &block)
  return to_enum(:transform_values!, with_key:) if block.nil?

  if with_key
    each_pair do |key, value|
      self[key] = block.call(value, key)
    end
  else
    og_transform_values!(&block)
  end
end

#value_where {|key, value| ... } ⇒ Object, ...

Returns the first value where the key-value pair satisfies the given condition

Examples:

Find first admin user by role

users = {
  alice: {name: "Alice", role: "admin"},
  bob: {name: "Bob", role: "user"},
  charlie: {name: "Charlie", role: "admin"}
}
users.value_where { |k, v| v[:role] == "admin" } # => {name: "Alice", role: "admin"}

Yields:

  • (key, value)

    Block that determines whether to include the value

Yield Parameters:

  • key (Object)

    The current key

  • value (Object)

    The current value

Yield Returns:

  • (Boolean)

    Whether to include this value

Returns:

  • (Object, nil)

    The first matching value or nil if none found

  • (Enumerator)

    If no block is given



504
505
506
507
508
# File 'lib/everythingrb/hash.rb', line 504

def value_where(&block)
  return to_enum(:value_where) if block.nil?

  find(&block)&.last
end

#values_where {|key, value| ... } ⇒ Array, Enumerator

Returns all values where the key-value pairs satisfy the given condition

Examples:

Find all admin users by role

users = {
  alice: {name: "Alice", role: "admin"},
  bob: {name: "Bob", role: "user"},
  charlie: {name: "Charlie", role: "admin"}
}
users.values_where { |k, v| v[:role] == "admin" }
# => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]

Yields:

  • (key, value)

    Block that determines whether to include the value

Yield Parameters:

  • key (Object)

    The current key

  • value (Object)

    The current value

Yield Returns:

  • (Boolean)

    Whether to include this value

Returns:

  • (Array)

    All matching values

  • (Enumerator)

    If no block is given



530
531
532
533
534
# File 'lib/everythingrb/hash.rb', line 530

def values_where(&block)
  return to_enum(:values_where) if block.nil?

  select(&block).values
end