Как использовать `Array # dig` и` Hash # dig`, введенные в Ruby 2.3?
Ruby 2.3 вводит новый метод в Array
и Hash
, называемый 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
Я не использую тройные вложенные плоские массивы. Какой реалистичный пример того, как это было бы полезно?
UPDATE
Оказывается, эти методы решают один из наиболее часто задаваемых вопросов Ruby. Ниже приведенные вопросы имеют примерно 20 дубликатов, все из которых решаются с помощью dig
:
Как избежать NoMethodError для отсутствующих элементов во вложенных хэшах, без повторных проверок nil?
Ruby Style: как проверить, существует ли вложенный элемент хеша
Ответы
Ответ 1
В нашем случае NoMethodError
из-за nil
ссылки являются наиболее распространенными ошибками, которые мы видим в наших производственных средах.
Новый 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
сам возвращает nil
, если какой-либо из ключей оказывается, что может привести к тому же классу ошибок на один шаг вниз по линии, так что это может быть хорошая идея обеспечить разумный дефолт. (Этот способ предоставления объекта, который всегда реагирует на ожидаемые методы, называется Null Object Pattern.)
Опять же, в вашем примере, пустая строка или что-то вроде "N/A", в зависимости от того, что имеет смысл:
user.dig(:user, :address, :street1) || ""
Ответ 2
Один из способов был бы связан с показателем оператора 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
Ответ 3
Это полезно для работы с глубоко вложенными хешами/массивами, которые могут быть, например, тем, что вы получите от вызова API.
В теории это экономит тонну кода, который в противном случае проверял бы на каждом уровне, существует ли другой уровень, без которого вы рискуете постоянными ошибками. На практике вам все еще может понадобиться много этого кода, так как dig
все равно будет создавать ошибки в некоторых случаях (например, если что-либо в цепочке является неключевым объектом).
Именно по этой причине ваш вопрос на самом деле действительно верный - dig
не видел использования, которое мы могли бы ожидать. Вот что прокомментировано здесь, например: почему никто не говорит о копании.
Чтобы dig
избегал этих ошибок, попробуйте гем KeyDial, который я написал, чтобы обернуть вокруг dig
и заставить его вернуть nil/default, если возникнет какая-либо ошибка.