可能重复:
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']
如果hash1中存在key1,key2和key3,这很好,但是如果key1不存在会怎么样?
然后我会得到NoMethodError: undefined method [] for nil:NilClass
。没有人喜欢这样。
到目前为止,我处理这个有条件的事情:
if my_hash['key1'] && my_hash['key1']['key2']
...
这是否合适,还有其他Rubiest方式吗?
答案 0 :(得分:164)
有很多方法可以解决这个问题。
如果您使用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] }
或者使用嵌套查找方法扩展Hash或仅扩展目标哈希对象
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']
答案 1 :(得分:6)
您也可以使用Object#andand。
my_hash['key1'].andand['key2'].andand['key3']
答案 2 :(得分: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方法,因为它们都以新的哈希值作为默认值进行操作,但这会将细节移动到创建时间。 不可否认,这有点作弊:它永远不会返回'nil'只是一个空的哈希,它是动态创建的。根据您的使用情况,这可能会造成浪费。
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)如果你觉得monadic,你可以看看这个Maybe