Hash#delete はブロックを受け取ることを初めて知った。そして、これがとても便利だったのでまとめておく。
Hash#delete は、
h = { a: 'aaa', b: 'bbb' }
h.delete(:a) #=> 'aaa'
h.delete(:nonexistent_key) #=> nil
p h #=> { b: 'bbb' }
のように、指定したキーを取り除くことができるメソッドで、
nil を返すという特徴を持っている。
そして、ブロックを渡した場合、
h.delete(:x) { |key| "#{key} は見つかりませんでした" } #=> ":x は見つかりませんでした。"
のように、指定したキーが見つからなかった場合にブロックが評価され、その結果が戻り値となる。
キーが見つからなかった場合の戻り値についてまとめると、
nil を返すとなる。
さて、これの何が便利だったかというと、つい最近 prawn-emoji のこの部分 の実装をするときに、この仕様のおかげでシンプルに書くことができた。
# == Additional Options
# <tt>:emoji</tt>:: <tt>boolean</tt>. Whether or not to draw an emoji [true]
def draw_text!(text, options)
draw_emoji = options.delete(:emoji) { true }
if draw_emoji && Emoji::Drawer.drawable?(text)
emoji_drawer.draw(text.to_s, options)
else
super
end
end
このコードは、既存の draw_text!を拡張する実装である。L#4 で Hash#delete を使っており、
draw_emoji = options.delete(:emoji) { true }
options の :emoji キーを削除し、値(boolean) を draw_emoji に格納する:emoji キーが存在しない場合は true を draw_emoji に格納するということをこの一行で実現することができる。
:emoji option の仕様について少し補足しておくと、
:emoji は拡張した option なので、L#10 の super に options を渡す際に削除しておきたい:emoji はデフォルト true にしたいという背景があった。
そして、元々、この部分(L#4)は、Hash#fetch(key, default) を使って実装していたが、(1) の要件を満たすためには、
どうしても別途 :emoji キーを消すコードを入れないといけなかった。
# == Additional Options
# <tt>:emoji</tt>:: <tt>boolean</tt>. Whether or not to draw an emoji [true]
def draw_text!(text, options)
draw_emoji = options.fetch(:emoji, true) #=> Hash#fetch を使った場合
options.delete(:emoji) #=> 別途 :emoji キーを削除しないといけない
if draw_emoji && Emoji::Drawer.drawable?(text)
emoji_drawer.draw(text.to_s, options)
else
super
end
end
細かいことだが、Hash#delete を使うとこれをスッキリ書くことができる (読みやすいかどうかは別)
See also Ruby2.7.0 リファレンスマニュアル > Hash クラス
ちなみに、
Hash#delete の "取り除いたキーの値を返す" という特徴を使って、
draw_emoji = options.delete(:emoji) || true
という方法も考えられるが、これは正しく動作しない。emoji: false のとき、draw_emoji は true になってしまうからだ。