我正在寻找一种避免在深度嵌套的哈希表中每个级别检查nil
的好方法。例如:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
这需要进行三个检查,会导致代码非常丑陋。有没有什么方法可以避免这种情况?
我正在寻找一种避免在深度嵌套的哈希表中每个级别检查nil
的好方法。例如:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
这需要进行三个检查,会导致代码非常丑陋。有没有什么方法可以避免这种情况?
写一次丑陋的代码,然后隐藏它
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
虽然这是一个非常老的问题,但这个答案可能对一些像我这样没有考虑过“begin rescue”控制结构表达式的stackoverflow用户有用。
我会使用try catch语句(在ruby语言中为begin rescue)来实现:
begin
name = params[:company][:owner][:name]
rescue
#if it raises errors maybe:
name = 'John Doe'
end
危险但有效:
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"链类似的风格。
你不需要访问原始哈希定义 - 你可以在使用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
}
但这并不能帮助你处理现有的代码,因为你依赖于未找到值来返回一个假值(例如,nil),如果你执行任何上面链接的“正常”自动创建哈希表的操作,那么你最终会得到一个空的哈希表,它被评估为“true”。
你可以像这样做——它只检查定义了的值并返回它们。你不能通过这种方式设置它们,因为我们无法知道调用是否在赋值的左侧。
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 中所熟知和喜爱的真正的自动化创建。
简而言之:params&.dig(:company, :owner, :name)
从Ruby 2.3.0开始:
你也可以使用称为“安全导航运算符”的 &.
来实现:params&.[](:company)&.[](:owner)&.[](:name)
。这个是完全安全的。
在params
上使用dig
实际上并不安全,因为如果params
为空,params.dig
将失败。
但是你可以将两者结合起来使用:params&.dig(:company, :owner, :name)
。
因此,以下任何一种方法都是安全的:
params&.[](:company)&.[](:owner)&.[](:name)
params&.dig(:company, :owner, :name)
为了超越dig
,可以尝试我编写的KeyDial gem。这本质上是dig
的一个包装器,但有一个重要的区别,它永远不会给你带来错误。
dig
如果链中的对象是某种类型,无法进行dig
操作,仍然会报错。
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
?
运算符。实际上,我对等效运算符很感兴趣。您仍然可以扩展哈希类并添加该运算符。 - Pasta