In Ruby 3,
hash => {a:}
works similarly to JS
{ a } = hash
except it throws an exception if :a
isn't a key in hash
instead of assigning a = nil
. Ok, we can do
hash => {a:} rescue nil
except this gives undesired result if some keys are present and others missing:
{a: 1, b: 2} => {b:, c:} rescue nil
puts "b: #{b}, c: #{c}"
shows b
and c
are both nil
instead of the desired b = 2, c = nil
. Is there a simple way to get that result? Or even more generally, emulate JS hash destructuring with non-nil
default values?
You can work around missing key error by making an object that responds to deconstruct_keys
:
hash = {a: 1}
H(hash) => x:,y:,a:
x # => nil
y # => nil
a # => 1
H(hash, "default") => x:,y:,a:
x # => "default"
y # => "default"
a # => 1
H(hash, x: :z) => x:,y:,a:
x # => :z
y # => nil
a # => 1
H(hash, "default", x: :z) => x:,y:,a:
x # => :z
y # => "default"
a # => 1
deconstruct_keys
is called when hash pattern is used => x:,y:,a:
, these keys are passed as argument:
# DelegateClass to make H be like a hash, but pattern match differently.
# (it works just fine without DelegateClass)
#
# H.new({a: 1}) # => {:a=>1}
#
class H < DelegateClass(Hash)
def initialize(hash, default = nil, **defaults)
# since hash needs to be duped anyway
@hash = defaults.merge hash # defaults for specific key
@default = default # default for any missing keys
super(@hash) # get delegation going
end
def deconstruct_keys keys
missing = keys - @hash.keys # find what's missing
missing.each { |k| @hash[k] = @default } # add it to hash with default value
@hash # return for pattern matching
end
end
# H() method, like a Hash() method.
def H(...)
H.new(...)
end