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
になってしまうからだ。