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

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

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

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

Ответы [ 17 ]

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

Напиши уродство один раз, затем спрячь его

def check_all_present(hash, keys)
  current_hash = hash
  keys.each do |key|
    return false unless current_hash[key]
    current_hash = current_hash[key]
  end
  true
end
1 голос
/ 07 декабря 2010

Можете ли вы избежать использования многомерного хэша и использовать вместо него

params[[:company, :owner, :name]]

или

params[[:company, :owner, :name]] if params.has_key?([:company, :owner, :name])

?

0 голосов
/ 26 июля 2017

Опасно, но работает:

class Object
        def h_try(key)
            self[key] if self.respond_to?('[]')
        end    
end

Мы можем сделать новое

   user = { 
     :first_name => 'My First Name', 
     :last_name => 'my Last Name', 
     :details => { 
        :age => 3, 
        :birthday => 'June 1, 2017' 
      } 
   }

   user.h_try(:first_name) # 'My First Name'
   user.h_try(:something) # nil
   user.h_try(:details).h_try(:age) # 3
   user.h_try(:details).h_try(:nothing).h_try(:doesnt_exist) #nil

Цепочка "h_try" соответствует стилю, аналогичному цепочке "try".

0 голосов
/ 26 июля 2013
require 'xkeys' # on rubygems.org

params.extend XKeys::Hash # No problem that we got params from somebody else!
name = params[:company, :owner, :name] # or maybe...
name = params[:company, :owner, :name, :else => 'Unknown']
# Note: never any side effects for looking

# But you can assign too...
params[:company, :reviewed] = true
0 голосов
/ 11 сентября 2017

TLDR; params&.dig(:company, :owner, :name)

По состоянию на Ruby 2.3.0:

Вы также можете использовать &., называемый «оператором безопасной навигации», как: params&.[](:company)&.[](:owner)&.[](:name). Это совершенно безопасно.

Использование dig на params на самом деле небезопасно, так как params.dig завершится ошибкой, если params равен нулю.

Однако вы можете объединить их как: params&.dig(:company, :owner, :name).

Так что любое из следующего безопасно для использования:

params&.[](:company)&.[](:owner)&.[](:name)

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

0 голосов
/ 09 января 2019

Просто, чтобы предложить один на dig, попробуйте камень KeyDial , который я написал. По сути, это оболочка для dig, но с той важной разницей, что она никогда не выдаст вам ошибку.

dig по-прежнему выдаст ошибку, если объект в цепочке имеет некоторый тип, который сам по себе не может быть dig ed.

hash = {a: {b: {c: true}, d: 5}}

hash.dig(:a, :d, :c) #=> TypeError: Integer does not have #dig method

В этой ситуации dig вам не поможет, и вам нужно вернуться не только к hash[:a][:d].nil? &&, но и к hash[:a][:d].is_a?(Hash) проверкам. KeyDial позволяет вам делать это без таких проверок или ошибок:

hash.call(:a, :d, :c) #=> nil
hash.call(:a, :b, :c) #=> true
0 голосов
/ 07 декабря 2010

Вам не нужен доступ к исходному определению хеша - вы можете переопределить метод [] на лету после того, как получите его с помощью h.instance_eval, например,

h = {1 => 'one'}
h.instance_eval %q{
  alias :brackets :[]
  def [] key
    if self.has_key? key
      return self.brackets(key)
    else
      h = Hash.new
      h.default = {}
      return h
    end
  end
}

Но это не поможет вам с кодом, который у вас есть, потому что вы полагаетесь на необнаруженное значение, чтобы вернуть ложное значение (например, ноль), и если вы делаете какой-либо из "нормальных" элементов авто-оживления, связанных выше вы получите пустой хеш для необнаруженных значений, который оценивается как "true".

Вы можете сделать что-то вроде этого - он только проверяет определенные значения и возвращает их. Вы не можете установить их таким образом, потому что у нас нет никакого способа узнать, находится ли вызов на LHS назначения.

module AVHash
  def deep(*args)
    first = args.shift
    if args.size == 0
      return self[first]
    else
      if self.has_key? first and self[first].is_a? Hash
        self[first].send(:extend, AVHash)
        return self[first].deep(*args)
      else
        return nil
      end
    end
  end
end      

h = {1=>2, 3=>{4=>5, 6=>{7=>8}}}
h.send(:extend, AVHash)
h.deep(0) #=> nil
h.deep(1) #=> 2
h.deep(3) #=> {4=>5, 6=>{7=>8}}
h.deep(3,4) #=> 5
h.deep(3,10) #=> nil
h.deep(3,6,7) #=> 8

Опять же, вы можете проверять только значения, но не назначать их. Так что это не настоящая автовивификация, как мы все знаем и любим в Perl.

...