Как избежать NoMethodError для отсутствующих элементов во вложенных хэшах, без повторных проверок на ноль? - PullRequest
28 голосов
/ 07 декабря 2010

Я ищу хороший способ избежать проверки на nil на каждом уровне в глубоко вложенных хэшах.Например:

name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]

Это требует трех проверок и делает код очень уродливым.Есть ли способ обойти это?

Ответы [ 17 ]

28 голосов
/ 06 января 2016

В Ruby 2.3.0 представлен новый метод с именем dig для Hash и Array, который полностью решает эту проблему.

name = params.dig(:company, :owner, :name)

Возвращает nil, если ключ отсутствует на каком-либо уровне.

Если вы используете версию Ruby старше 2.3, вы можете использовать ruby_dig gem или реализовать ее самостоятельно:

module RubyDig
  def dig(key, *rest)
    if value = (self[key] rescue nil)
      if rest.empty?
        value
      elsif value.respond_to?(:dig)
        value.dig(*rest)
      end
    end
  end
end

if RUBY_VERSION < '2.3'
  Array.send(:include, RubyDig)
  Hash.send(:include, RubyDig)
end
11 голосов
/ 07 декабря 2010

Лучший компромисс между функциональностью и ясностью ИМО - это Раганвальд andand.С этим вы бы сделали:

params[:company].andand[:owner].andand[:name]

Это похоже на try, но в этом случае читается намного лучше, так как вы по-прежнему отправляете сообщения как обычно, но с разделителем, который привлекает внимание ктот факт, что вы относитесь к nils специально.

6 голосов
/ 07 декабря 2010

Не знаю, хотите ли вы этого, но, может быть, вы могли бы сделать это?

name = params[:company][:owner][:name] rescue nil
4 голосов
/ 07 декабря 2010

Эквивалентно второму решению, предложенному пользователем mpd, только более идиоматичный Ruby:

class Hash
  def deep_fetch *path
    path.inject(self){|acc, e| acc[e] if acc}
  end
end

hash = {a: {b: {c: 3, d: 4}}}

p hash.deep_fetch :a, :b, :c
#=> 3
p hash.deep_fetch :a, :b
#=> {:c=>3, :d=>4}
p hash.deep_fetch :a, :b, :e
#=> nil
p hash.deep_fetch :a, :b, :e, :f
#=> nil
4 голосов
/ 07 декабря 2010

Возможно, вы захотите найти один из способов добавить авто-вивификацию к хэшам ruby. Существует несколько подходов, упомянутых в следующих потоках stackoverflow:

3 голосов
/ 07 декабря 2010

Если вы хотите заняться обезьянничанием, вы можете сделать что-то вроде этого class NilClass def [](anything) nil end end

Тогда вызов params[:company][:owner][:name] даст ноль, если в любой момент один из вложенных хэшей будет равен нулю.1006 * РЕДАКТИРОВАТЬ: Если вы хотите более безопасный маршрут, который также обеспечивает чистый код, вы можете сделать что-то вроде class Hash def chain(*args) x = 0 current = self[args[x]] while current && x < args.size - 1 x += 1 current = current[args[x]] end current end end

Код будет выглядеть так: params.chain(:company, :owner, :name)

3 голосов
/ 07 декабря 2010

Если это рельсы, используйте

params.try(:[], :company).try(:[], :owner).try(:[], :name)

Ой, подождите, это еще страшнее.; -)

2 голосов
/ 07 декабря 2010

Я бы написал так:

name = params[:company] && params[:company][:owner] && params[:company][:owner][:name]

Это не так чисто, как ?оператор в Io , но в Ruby этого нет.Ответ @ThiagoSilveira также хорош, но будет медленнее.

1 голос
/ 06 октября 2012

(Несмотря на то, что это действительно старый вопрос, возможно, этот ответ будет полезен для некоторых людей со стековым потоком, таких как я, которые не думают о выражении структуры управления "begin rescue".)

Я бы сделал это с помощью оператора try catch (начать спасение на языке ruby):

begin
    name = params[:company][:owner][:name]
rescue
    #if it raises errors maybe:
    name = 'John Doe'
end
1 голос
/ 01 апреля 2011

Do:

params.fetch('company', {}).fetch('owner', {})['name']

Также на каждом шаге вы можете использовать соответствующий метод, встроенный в NilClass, для выхода из nil, если это массив, строка или число.Просто добавьте to_hash в инвентарь этого списка и используйте его.

class NilClass; def to_hash; {} end end
params['company'].to_hash['owner'].to_hash['name']
...