如何使用 Ruby 2.3 中引入的 Array#dig 和 Hash#dig?

How do I use Array#dig and Hash#dig introduced in Ruby 2.3?

Ruby 2.3 在 ArrayHash 上引入了一种名为 dig 的新方法。我在有关新版本的博客文章中看到的示例是人为且令人费解的:

# Hash#dig
user = {
  user: {
    address: {
      street1: '123 Main street'
    }
  }
}

user.dig(:user, :address, :street1) # => '123 Main street'

# Array#dig
results = [[[1, 2, 3]]]
results.dig(0, 0, 0) # => 1

我没有使用三层嵌套平面数组。什么是这将如何有用的现实示例?

更新

事实证明,这些方法解决了最常见的 Ruby 问题之一。下面的问题大约有 20 次重复,所有这些问题都是通过使用 dig:

解决的

How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?

Ruby Style: How to check whether a nested hash element exists

一种方法是结合 splat 运算符从一些未知文档模型中读取。

some_json = JSON.parse( '{"people": {"me": 6, ... } ...}' )
# => "{"people" => {"me" => 6, ... }, ... }
a_bunch_of_args = response.data[:query]
# => ["people", "me"]
some_json.dig(*a_bunch_of_args)
# => 6

在我们的例子中,由于 nil 引用导致的 NoMethodError 是迄今为止我们在生产环境中看到的最常见错误。

新的 Hash#dig 允许您在访问嵌套元素时省略 nil 检查。由于散列最适用于数据结构未知或易变的情况,因此获得官方支持非常有意义。

让我们举个例子。以下:

user.dig(:user, :address, :street1)

等于:

user[:user][:address][:street1]

user[:user]user[:user][:address]nil 的情况下,这将导致运行时错误。

而是相当于下面的,也就是现在的成语:

user[:user] && user[:user][:address] && user[:user][:address][:street1]

请注意,将在别处创建的符号列表传递到 Hash#dig 是多么微不足道,而从这样的列表中重新创建后一个构造并不是很简单。 Hash#dig 允许您轻松地进行动态访问,而不必担心 nil 引用。

显然 Hash#dig 也短了很多。


需要注意的重要一点是 Hash#dig 本身 returns nil 如果任何键结果是,这可能导致相同的 class 的错误,所以提供一个合理的默认值是个好主意。 (这种提供始终响应预期方法的对象的方式称为 Null Object Pattern。)

同样,在您的示例中,空字符串或类似 "N/A" 的内容,具体取决于什么有意义:

user.dig(:user, :address, :street1) || ""

它对于处理深度嵌套的 Hashes/Arrays 非常有用,例如,这可能是您从 API 调用中得到的结果。

理论上它节省了大量代码,否则会在每个级别检查是否存在另一个级别,否则您将面临不断出错的风险。 在实践中你可能仍然需要很多这样的代码,因为dig在某些情况下仍然会产生错误(例如,如果链中的任何东西是非键控对象。)

正是出于这个原因,您的问题实际上是有效的 - dig 没有看到我们可能期望的用法。例如,这里对此进行了评论:Why nobody speaks about dig.

为了让 dig 避免这些错误,请尝试 KeyDial gem,我写它来包围 dig 并强制它成为 return nil/default 如果出现任何错误。