Ruby - доступ к многомерному хешу и исключению доступа к объекту nil

Возможный дубликат:
Ruby: Нильс в заявлении IF
Есть ли чистый способ избежать вызова метода на nil во вложенном параметре hash?

Скажем, я пытаюсь получить доступ к хэшу следующим образом:

my_hash['key1']['key2']['key3']

Это хорошо, если в хэше (es) существуют ключевые1, key2 и key3, но что, если, например, key1 не существует?

Тогда я получил бы NoMethodError: undefined method [] for nil:NilClass. И никто не любит это.

До сих пор я занимаюсь этим выполнением условного типа:

if my_hash['key1'] && my_hash['key1']['key2']...

Это уместно, есть ли другой способ Rubiest?

Ответ 1

Существует много подходов к этому.

Если вы используете Ruby 2.3 или выше, вы можете использовать dig

my_hash.dig('key1', 'key2', 'key3')

Множество людей придерживаются простого рубина и цепочки испытаний &&.

Вы можете использовать stdlib Hash # fetch:

my_hash.fetch('key1', {}).fetch('key2', {}).fetch('key3', nil)

Некоторым нравится привязка метода ActiveSupport # try.

my_hash.try(:[], 'key1').try(:[], 'key2').try(:[], 'key3')

Другие используют andand

myhash['key1'].andand['key2'].andand['key3']

Некоторые люди думают, что egocentric nils - хорошая идея (хотя кто-то может охотиться на вас и пытать вас, если они обнаружат, что вы это делаете).

class NilClass
  def method_missing(*args); nil; end
end

my_hash['key1']['key2']['key3']

Вы можете использовать Enumerable # reduce (или добавить псевдоним).

['key1','key2','key3'].reduce(my_hash) {|m,k| m && m[k] }

Или, возможно, расширить хэш или только ваш целевой хэш-объект с помощью метода вложенного поиска

module NestedHashLookup
  def nest *keys
    keys.reduce(self) {|m,k| m && m[k] }
  end
end

my_hash.extend(NestedHashLookup)
my_hash.nest 'key1', 'key2', 'key3'

О, и как мы могли бы забыть maybe monad?

Maybe.new(my_hash)['key1']['key2']['key3']

Ответ 2

Условия my_hash['key1'] && my_hash['key1']['key2'] не чувствуют DRY.

Альтернатива:

1) autovivification magic. Из этого сообщения:

def autovivifying_hash
   Hash.new {|ht,k| ht[k] = autovivifying_hash}
end

Затем, в вашем примере:

my_hash = autovivifying_hash     
my_hash['key1']['key2']['key3']

Он похож на подход Hash.fetch, поскольку оба они работают с новыми хэшами как значения по умолчанию, но это перемещает детали во время создания. По общему признанию, это немного обман: он никогда не вернет "нуль" только пустой хеш, который создается "на лету". В зависимости от вашего варианта использования это может быть расточительным.

2) Откажитесь от структуры данных с помощью механизма поиска и обработайте незанятый случай за кулисами. Простейший пример:

def lookup(model, key, *rest) 
    v = model[key]
    if rest.empty?
       v
    else
       v && lookup(v, *rest)
    end
end
#####

lookup(my_hash, 'key1', 'key2', 'key3')
=> nil or value

3) Если вы чувствуете монадическое, вы можете взглянуть на это, Maybe

Ответ 3

Вы также можете использовать Объект # и и.

my_hash['key1'].andand['key2'].andand['key3']