Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[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?

@brooks

This comment has been minimized.

Copy link

@brooks brooks commented Feb 21, 2020

delete_if

@buraga-dmitrii

This comment has been minimized.

Copy link

@buraga-dmitrii buraga-dmitrii commented Aug 6, 2020

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