web-dev-qa-db-ja.com

nilで「未定義のメソッド」エラーを回避するためのハッシュの.try()と同等ですか?

Railsでは、エラーを回避するために値が存在しない場合に以下を実行できます。

@myvar = @comment.try(:body)

ハッシュを深く掘り下げてエラーを取得したくない場合、同等のものは何ですか?

@myvar = session[:comments][@comment.id]["temp_value"] 
# [:comments] may or may not exist here

上記の場合、session[:comments]try[@comment.id]は機能しません。何だろう?

161
sscirrus

tryの前に.を置くのを忘れました:

@myvar = session[:comments].try(:[], @comment.id)

[]は、[@comment.id]を実行したときのメソッドの名前です。

258
Andrew Grimm

Ruby 2.3.0-preview1の発表 には、安全なナビゲーション演算子の導入が含まれています。

C#、Groovy、およびSwiftに既に存在する安全なナビゲーション演算子が導入され、obj&.fooとしてのnil処理が容易になりました。 Array#DigおよびHash#Digも追加されます。

これは、コード2.3以降では

account.try(:owner).try(:address)

に書き換えることができます

account&.owner&.address

ただし、&#tryに置き換わるものではないことに注意する必要があります。この例を見てください:

> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil

Array#DigおよびHash#Digという同様の方法も含まれています。だから今これ

city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)

に書き換えることができます

city = params.Dig(:country, :state, :city)

繰り返しますが、#Dig#tryの動作を複製していません。そのため、値を返すことに注意してください。たとえば、params[:country]が整数を返す場合、TypeError: Integer does not have #Dig methodが発生します。

61
baxang

最も美しい解決策は古い MladenJablanovićによる回答 です。コードをそのまま見たい場合は、直接.try()呼び出しを使用した場合よりも深くハッシュを掘ることができます。 :

class Hash
  def get_deep(*fields)
    fields.inject(self) {|acc,e| acc[e] if acc}
  end
end

文字列と配列も:[]に応答するため、さまざまなオブジェクト(特にparams)に注意する必要がありますが、返される値は必要なものではない場合があり、配列はインデックスとして使用される文字列またはシンボルの例外を発生させます。

これが、このメソッドの推奨形式(下)で(通常はい)の理由です(通常は良い).is_a?(Hash):の代わりに.respond_to?(:[])のテストが使用されます:

class Hash
  def get_deep(*fields)
    fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
  end
end

a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}

puts a_hash.get_deep(:one, :two               ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three       ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr            ).inspect    # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect    # => nil

最後の例は、例外を発生させます:このarrayい "is_a?(ハッシュ)"によって保護されていなかった場合、 "Symbol as array index(TypeError)" 。

25
Arsen7

try とハッシュの適切な使用は@sesion.try(:[], :comments)です。

@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
15

更新:Ruby 2.3の使用- #Dig

[]に応答するほとんどのオブジェクトは整数の引数を必要としますが、ハッシュはオブジェクト(文字列や記号など)を受け入れる例外です。

以下は、ネストされた配列、ハッシュ、および[]に渡される整数を期待する他のオブジェクトをサポートする Arsen7の答え のわずかに堅牢なバージョンです。

だれかが[]を実装するオブジェクトを作成し、はInteger引数を受け入れないので、それは絶対確実ではありません。ただし、このソリューションは一般的なケースでうまく機能します。 JSON(HashとArrayの両方を含む)からネストされた値を取得する:

class Hash
  def get_deep(*fields)
    fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
  end
end

Arsen7のソリューションと同じように使用できますが、配列などもサポートしています。

json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }

json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
14
Benjamin Dobell
@myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"]

Ruby 2.0以降では、次のことができます。

@myvar = session[:comments].to_h[@comment.id].to_h["temp_value"]

Ruby 2.3から、次のことができます。

@myvar = session.Dig(:comments, @comment.id, "temp_value")
12
sawa

Ruby 2.3以降、これは少し簡単になります。 tryステートメントをネストしたり、独自のメソッドを定義する代わりに、Hash#Digdocumentation )を使用できます。

h = { foo: {bar: {baz: 1}}}

h.Dig(:foo, :bar, :baz)           #=> 1
h.Dig(:foo, :zot)                 #=> nil

または、上記の例では:

session.Dig(:comments, @comment.id, "temp_value")

これには、上記の例のいくつかよりもtryに似ているという利点もあります。引数のいずれかがハッシュにnilを返す場合、nilが返されます。

11
Steve Smith

params[:user][:email]を検索したいが、userparamsにあるかどうかはわかりません。それから

あなたが試すことができます:

params[:user].try(:[], :email)

niluserが存在しない場合、またはemailuserに存在しない場合)または、emailuserの値を返します。

11
Rajesh Paul

別のアプローチ:

@myvar = session[:comments][@comment.id]["temp_value"] rescue nil

これはあまりにも隠すことができるので、少し危険かもしれません。個人的には気に入っています。

さらに制御したい場合は、次のようなものを検討できます。

def handle # just an example name, use what speaks to you
    raise $! unless $!.kind_of? NoMethodError # Do whatever checks or 
                                              # reporting you want
end
# then you may use
@myvar = session[:comments][@comment.id]["temp_value"] rescue handle
6
Nicolas Goy

これを行うとき:

myhash[:one][:two][:three]

「[]」メソッドへの呼び出しのチェーンをチェーンしているだけで、myhash [:one]がnilを返すとエラーが発生します。nilには[]メソッドがないためです。そのため、1つの単純でややハックな方法は、nilを返す[]メソッドをNiclassに追加することです。これは、次のようにRailsアプリで設定します。

メソッドを追加します。

#in lib/Ruby_extensions.rb
class NilClass
  def [](*args)
    nil
  end
end

ファイルが必要です:

#in config/initializers/app_environment.rb
require 'Ruby_extensions'

今、あなたは恐れることなくネストされたハッシュを呼び出すことができます:私はここでコンソールでデモしています:

>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
2
Max Williams

Andrewの答えは、最近これを再試行したときにうまくいきませんでした。たぶん何かが変わった?

@myvar = session[:comments].try('[]', @comment.id)

'[]'は、シンボル:[]ではなく引用符で囲まれています

1
claptimes