ちなみに

火曜日の空は僕を押しつぶした。

2.2だってdigしたい!

Ruby 2.3のpreview1がリリースされ、クリスマスの足音が聞こえてきた昨今、みなさまいかがお過ごしでしょうか。 駆け込みでいくつかの機能が取り込まれていますが、そのなかでもArrayHashStructに実装された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 だったら即返すというシンプルな実装です。

d.hatena.ne.jp

ArrayHashだけかと思っていたら、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]

まあ、引数が扱えなかったりして不十分なんだだけれど。

ご活用ください。ネタです。