nil
深くネストされたハッシュの各レベルでのチェックを回避するための良い方法を探しています。例えば:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
これには3つのチェックが必要であり、非常に醜いコードになります。これを回避する方法はありますか?
nil
深くネストされたハッシュの各レベルでのチェックを回避するための良い方法を探しています。例えば:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
これには3つのチェックが必要であり、非常に醜いコードになります。これを回避する方法はありますか?
Ruby 2.3.0では、両方で呼び出される新しいメソッドがdig
導入されました。これにより、この問題が完全に解決されます。Hash
Array
name = params.dig(:company, :owner, :name)
nil
キーがいずれかのレベルで欠落している場合に返されます。
2.3より前のバージョンのRubyを使用している場合は、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
機能性と明快さのIMOの間の最良の妥協点は、Raganwaldのandand
です。それで、あなたはするでしょう:
params[:company].andand[:owner].andand[:name]
これはに似てtry
いますが、通常のようにメッセージを送信しているため、この場合ははるかに読みやすくなりますが、間に区切り文字を付けると、nilsを特別に処理していることに注意が必要です。
それがあなたが望むものかどうかはわかりませんが、多分あなたはこれを行うことができますか?
name = params[:company][:owner][:name] rescue nil
ルビーハッシュに自動活性化を追加する方法の1つを調べたいと思うかもしれません。次のstackoverflowスレッドで言及されているアプローチがいくつかあります。
ユーザーが提案した2番目のソリューションと同等で、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
レールの場合は、
params.try(:[], :company).try(:[], :owner).try(:[], :name)
ああ、待って、それはさらに醜いです。;-)
モンキーパッチを始めたいなら、このようなことをすることができます
class NilClass
def [](anything)
nil
end
end
次に、ネストされたハッシュのいずれかがnilの場合、を呼び出すとparams[:company][:owner][:name]
nilが生成されます。
編集:クリーンなコードも提供するより安全なルートが必要な場合は、次のようなことを行うことができます
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)
多次元ハッシュの使用を避けて、
params[[:company, :owner, :name]]
また
params[[:company, :owner, :name]] if params.has_key?([: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
行う:
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']
(これは非常に古い質問ですが、この回答は、「レスキューの開始」制御構造の表現を考えていなかった私のようなスタックオーバーフローの人々にとって役立つかもしれません。)
私はtrycatchステートメントでそれを行います(Ruby言語でレスキューを開始します):
begin
name = params[:company][:owner][:name]
rescue
#if it raises errors maybe:
name = 'John Doe'
end
元のハッシュ定義にアクセスする必要はありません。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」と評価されます。
あなたはこのようなことをすることができます-それは定義された値をチェックしてそれらを返すだけです。呼び出しが割り当ての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でそれを知っていて、愛しているので、それは本当の自動活性化ではありません。
危険ですが機能します:
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」チェーンと同様のスタイルに従います。
TLDR;params&.dig(:company, :owner, :name)
Ruby 2.3.0以降:
&.
「安全なナビゲーション演算子」と呼ばれるものを次のように使用することもできますparams&.[](:company)&.[](:owner)&.[](:name)
。これは完全に安全です。
dig
onを使用することは、がnilの場合に失敗するparams
ため、実際には安全ではありません。params.dig
params
ただし、次のように2つを組み合わせることができますparams&.dig(:company, :owner, :name)
。
したがって、次のいずれかを安全に使用できます。
params&.[](:company)&.[](:owner)&.[](:name)
params&.dig(:company, :owner, :name)
ワンアップを提供するために、私が書いたKeyDialgemdig
を試してみてください。これは基本的にのラッパーですが、エラーが発生することはないという重要な違いがあります。dig
dig
dig
チェーン内のオブジェクトがそれ自体で編集できないタイプの場合でも、エラーを吐き出します。
hash = {a: {b: {c: true}, d: 5}}
hash.dig(:a, :d, :c) #=> TypeError: Integer does not have #dig method
この状況では役に立たないので、チェックだけでなくチェックdig
に戻る必要があります。KeyDialを使用すると、このようなチェックやエラーなしでこれを行うことができます。hash[:a][:d].nil? &&
hash[:a][:d].is_a?(Hash)
hash.call(:a, :d, :c) #=> nil
hash.call(:a, :b, :c) #=> true