web-dev-qa-db-ja.com

Ruby-多次元ハッシュにアクセスし、nilオブジェクトへのアクセスを避ける

可能性のある複製:
Ruby:IFステートメントのNils
ネストされたparamsハッシュでnilのメソッドを呼び出すことを避けるためのクリーンな方法はありますか?

このようなハッシュにアクセスしようとするとしましょう:

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

Key1、key2、key3がハッシュに存在する場合、これは素晴らしいことですが、たとえばkey1が存在しない場合はどうでしょうか?

次に、NoMethodError: undefined method [] for nil:NilClass。そして誰もそれが好きではありません。

これまでのところ、私はこれを次のような条件付きで処理しています:

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

これは適切ですか、そうする他の最もルービィな方法はありますか?

85
Nobita

これには多くのアプローチがあります。

Ruby 2.3以上を使用する場合、 Dig を使用できます

my_hash.Dig('key1', 'key2', 'key3')

たくさんの人が平野Rubyに固執し、&&ガードテスト。

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.new(my_hash)['key1']['key2']['key3']
168
dbenhur

Object#andand を使用することもできます。

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

条件my_hash['key1'] && my_hash['key1']['key2']感じない [〜#〜] dry [〜#〜]

代替案:

1) autovivification magic。その投稿から:

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)モナドを感じたら、これを見てみることができます 多分

5
inger