Skip to content

Instantly share code, notes, and snippets.

@O-I
Last active August 2, 2023 17:31
Show Gist options
  • Save O-I/c40d89c4aeb16ae2fc3f to your computer and use it in GitHub Desktop.
Save O-I/c40d89c4aeb16ae2fc3f to your computer and use it in GitHub Desktop.
[TIx 4] Remove key-value pairs from a hash and return the hash

Note: As of Ruby 3.0.0, Hash#except is now part of the language. However, Ruby does not implement Hash#except!.

Sometimes I want to remove a specific key-value pair from a Ruby hash and get the resulting hash back. If you're using Rails or ActiveSupport you can accomplish this using Hash#except:

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

# note, the original hash is not modified
hash                # => { a: 1, b: 2, c: 3 }

# it also works with multiple key-value pairs
hash.except(:a, :c) # => { b: 2 }

# and returns the original hash if the key doesn't exist
hash.except(:q)    # => { a: 1, b: 2, c: 3 }

# there's also Hash#except!
hash.except!(:b)   # => { a: 1, c: 3 }

# this method does modify the original hash
hash               # => { a: 1, c: 3 }

# and also returns the original hash if the key doesn't exist
hash.except!(:b)   # => { a: 1, c: 3 }

Great. But what if we want to do this with plain old Ruby?

hash = { a: 1, b: 2, c: 3, d: 4 }

# the destructive case with removing exactly one key-value pair is easy
hash.tap { |h| h.delete(:a) }                         # => { b: 2, c: 3, d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# the non-destructive case with one key-value pair isn't much harder
hash.dup.tap { |h| h.delete(:c) }                     # => { b: 2, d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# but when we want to remove several key-value pairs, it gets a little messy
hash.dup.tap { |h| [:b, :c].map { |k| h.delete(k) } } # => { d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# using reject makes it more palatable
hash.reject { |k, _| [:b, :c].include? k }            # => { d: 4 }
hash                                                  # => { b: 2, c: 3, d: 4 }

# and, of course, we can also use reject!
hash.reject! { |k, _| [:b, :c].include? k }           # => { d: 4 }
hash                                                  # => { d: 4 }

# not so fast...reject! doesn't behave the way we want when the keys aren't there :-(
hash.reject! { |k, _| [:b, :c].include? k }           # => nil
hash                                                  # => { d: 4 }

# so maybe tap-map-delete is the best we can do here
hash.tap { |h| [:b, :c].map { |k| h.delete(k) } }     # => { d: 4 }
hash                                                  # => { d: 4 }

How would you choose to do it?

@buraga-dmitrii
Copy link

But if want to remove keys in nested hash?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment