2012-04-12 54 views
70

可能重複:
Ruby: Nils in an IF statement
Is there a clean way to avoid calling a method on nil in a nested params hash?紅寶石 - 訪問多維散列,避免接入無對象

比方說,我嘗試訪問這樣的哈希:

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

這很好,如果key1,ke y2和key3存在於散列(es)中,但如果key1不存在?

然後我會得到NoMethodError: undefined method [] for nil:NilClass。沒有人喜歡這個。

到目前爲止,我處理這個做一個條件,如:

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

這是適當的,有沒有這樣做的任何其他方式Rubiest?

+1

對不起社區。我試圖尋找這個,並找不到它。如果合適,關閉。 – Nobita 2012-04-12 20:48:12

+0

接受的答案提到除了正確的Ruby 2.3+之外的每種可能的方法:http://ruby-doc.org/core-2.3.1/Hash.html#method-i-dig – 2017-01-08 22:13:12

回答

139

有很多方法。

如果您使用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單子?

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

我決定採取獲取方法。但感謝所有的建議。 – Nobita 2012-04-12 20:44:25

+0

你也可以試試monadic gem,它有一個Maybe(和其他monad),它可以幫助處理異常 – 2012-05-04 05:03:51

+0

你在結尾使用'rescue nil'有什麼想法? – jakeonrails 2013-05-17 18:46:40

5

條件my_hash['key1'] && my_hash['key1']['key2']不覺得DRY

替代方案:

1)autovivification魔術。從該職位:

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

5

你也可以使用Object#andand

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