30

nil深くネストされたハッシュの各レベルでのチェックを回避するための良い方法を探しています。例えば:

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

これには3つのチェックが必要であり、非常に醜いコードになります。これを回避する方法はありますか?

4

16 に答える 16

34

Ruby 2.3.0では、両方で呼び出される新しいメソッドがdig導入されました。これにより、この問題が完全に解決されます。HashArray

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
于 2016-01-06T00:25:48.557 に答える
11

機能性と明快さのIMOの間の最良の妥協点は、Raganwaldのandandです。それで、あなたはするでしょう:

params[:company].andand[:owner].andand[:name]

これはに似てtryいますが、通常のようにメッセージを送信しているため、この場合ははるかに読みやすくなりますが、間に区切り文字を付けると、nilsを特別に処理していることに注意が必要です。

于 2010-12-06T23:20:02.997 に答える
7

それがあなたが望むものかどうかはわかりませんが、多分あなたはこれを行うことができますか?

name = params[:company][:owner][:name] rescue nil
于 2010-12-06T23:11:46.260 に答える
4

ルビーハッシュに自動活性化を追加する方法の1つを調べたいと思うかもしれません。次のstackoverflowスレッドで言及されているアプローチがいくつかあります。

于 2010-12-07T00:39:15.967 に答える
4

ユーザーが提案した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
于 2010-12-07T18:58:56.750 に答える
3

レールの場合は、

params.try(:[], :company).try(:[], :owner).try(:[], :name)

ああ、待って、それはさらに醜いです。;-)

于 2010-12-06T23:07:04.743 に答える
3

モンキーパッチを始めたいなら、このようなことをすることができます

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)

于 2010-12-06T23:13:53.157 に答える
2

私はこれを次のように書きます:

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

それは?ほどきれいではありません Ioの演算子ですが、Rubyにはありません。@ThiagoSilveiraによる回答も良いですが、遅くなります。

于 2010-12-06T23:21:50.433 に答える
1

多次元ハッシュの使用を避けて、

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

また

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

代わりは?

于 2010-12-06T23:50:25.140 に答える
1

醜さを一度書いて、それを隠す

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
于 2010-12-07T01:30:34.943 に答える
1

行う:

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']
于 2011-04-01T13:48:57.647 に答える
1

(これは非常に古い質問ですが、この回答は、「レスキューの開始」制御構造の表現を考えていなかった私のようなスタックオーバーフローの人々にとって役立つかもしれません。)

私はtrycatchステートメントでそれを行います(Ruby言語でレスキューを開始します):

begin
    name = params[:company][:owner][:name]
rescue
    #if it raises errors maybe:
    name = 'John Doe'
end
于 2012-10-06T13:51:47.513 に答える
0

元のハッシュ定義にアクセスする必要はありません。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でそれを知っていて、愛しているので、それは本当の自動活性化ではありません。

于 2010-12-07T05:09:11.177 に答える
0

危険ですが機能します:

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」チェーンと同様のスタイルに従います。

于 2017-07-26T17:05:03.857 に答える
0

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

Ruby 2.3.0以降:

&.「安全なナビゲーション演算子」と呼ばれるものを次のように使用することもできますparams&.[](:company)&.[](:owner)&.[](:name)。これは完全に安全です。

digonを使用することは、がnilの場合に失敗するparamsため、実際には安全ではありません。params.digparams

ただし、次のように2つを組み合わせることができますparams&.dig(:company, :owner, :name)

したがって、次のいずれかを安全に使用できます。

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

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

于 2017-09-11T15:22:38.477 に答える
0

ワンアップを提供するために、私が書いたKeyDialgemdigを試してみてください。これは基本的にのラッパーですが、エラーが発生することはないという重要な違いがあります。dig

digdigチェーン内のオブジェクトがそれ自体で編集できないタイプの場合でも、エラーを吐き出します。

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
于 2019-01-09T01:27:32.997 に答える