Ruby 2.3のpreview1がリリースされ、クリスマスの足音が聞こえてきた昨今、みなさまいかがお過ごしでしょうか。
駆け込みでいくつかの機能が取り込まれていますが、そのなかでもArray
やHash
、Struct
に実装されたdig
がかわいくて気に入っています。
ネストしたデータ構造から値を取り出す処理として、古くから野良実装が書かれてきたものですが、このたび正式に言語機能として取り込まれるようです。
hash = { a: { b: { c: 12 } } } hash.dig(:a, :b, :c) #=> 12 array = [[:a, [1, 2, 3]], [:b, [4, 5, 6]]] p array.dig(0, 1, 1) #=> 2
こういうことが出来るもので、Hash
での簡単な実装としては以下のように書けます。
class Hash def dig(*keys) keys.inject(self) do |h, k| h[k] or return nil end end end
渡されたキーで順次値を取り出していき、nil
だったら即返すというシンプルな実装です。
Array
とHash
だけかと思っていたら、Struct
にも追加されたようで、以下のようなことが出来るようになりました。
Node = Struct.new(:value, :left, :right) root = Node.new(2, Node.new(1, nil, nil), Node.new(3, nil, nil) ) p root.dig(:right, :value) #=> 3
なるほどーという感じなのですが、これクラスでも使えたらべんりだなあ、2.2でも動くといいなあということで昼休みに妄想していました。
module Digable refine Object do def dig(*keys) keys.inject(self) do |target, key| next target.[](key.to_i) if target.is_a?(Array) next target.[](key) if target.is_a?(Hash) target.__send__(key) rescue return nil end end end end
雑にこういうのを用意しておけばば2.2系でもdig
り放題、もちろんクラスでもdig
れます。
using Digable class Author < AR::Base belongs_to :book end class Book < AR::Base has_one :author end book = Book.find_by(title: "吾輩は猫である") book.dig(:author, :name) #=> 夏目漱石
ちなみに、これ何がうれしいかというと、2.3で追加されるobj&.meth
記法を使わなくても、安全なメソッド呼び出しが出来て、かつネスト出来るということは Swift の Optional Chaining のようなことが出来るのです。
root = Node.new(1, left: nil, right: nil) root.dig(:right, :left, :left, :value) #=> nil servers = [server1, server2, server3] servers.map {|s| s.dig(:interfaces, :eth1, :active?) } #=> [true, nil, false]
まあ、引数が扱えなかったりして不十分なんだだけれど。
ご活用ください。ネタです。