一つ目のキーがnilでエラーになってしまう問題
渡ってくるparams
<ActionController::Parameters {}>
[user] key
の[info] key
の値を取得したい。
info = params[:user][:info]
そもそも一つ目のキー(user
)が存在しないのでエラー吐く。
undefined method `[]' for nil:NilClass
このようなエラーを回避するため、
一つ目のキーがnilじゃない場合に、二つ目のキーを取得しに行いきたいときがあります。
やり方は色々ありますが、結論からいうと、dig
メソッドを使うことをおすすめします。
理由は後半で説明したいと思います。
1. &&で回避
一般的にはこの書き方で回避することが多いと思います
params = { }
info = params[:user][:info] if params[:user] && params[:user][:info]
# => nil
エラーは回避できますが、以下のように、ネストが増えるたびに記述が冗長になってしまい、見辛いのが本音。
params = { user: { info: { name: { first: :Suzuki } } } }
params[:user][:info][:name][:first] if params[:user] && params[:user][:info] && params[:user][:info][:name] && params[:user][:info][:name][:first]
# => :Suzuki
params[:user][:info][:full_name][:first] if params[:user] && params[:user][:info] && params[:user][:info][:full_name] && params[:user][:info][:full_name][:first]
# => nil
2. tryで回避
少し慣れない書き方ですが、paramsでtry
を使うには、以下の書き方でOK。
params = { }
info = params[:user].try(:[], :info)
# => nil
だいぶすっきりになりましたね。
[:user][:info]
が存在すればその値を返し、
もし[:user]
がnil
だったとしても、エラーを回避してnil
を返してくれます。
これで、どれだけ複数ネストしていてもエラーを吐くことなく取得することが可能になります
params = { user: { info: { name: { first: :Suzuki } } } }
info = params[:user].try(:[], :info).try(:[], :name).try(:[], :first)
# => :Suzuki
info = params[:user].try(:[], :info).try(:[], :full_name).try(:[], :fist)
# => nil
3. fechで回避
params = { }
info = params.fetch(:user, :info)
# => :info
この場合、エラーは回避できますが、もし第一引数がなければ第二引数である:info
を返すことになってしまいます。
params.fetch(受け取りたいキー, キーが存在しない場合の値)
また、第2引数はあくまでparams
のデフォルト値として扱うことになるので、ネストしたハッシュを取得するこができないので注意が必要です。
params = { user: { info: { name: { first: :Suzuki } } } }
# ネストした値は取得できない
info = params.fetch(:user, :info)
=> {:info=>{:name=>{:first=>:Suzuki, :last=>:Itiro}}}
info = params.fetch(:user, :info, :name)
=> ArgumentError: wrong number of arguments (given 3, expected 1..2)
今回の目的としては不採用でしょう。
4. digで回避
params = { }
info = params.dig(:user, :info)
# => nil
キーが存在すればその値を返し、存在しなければnil
を返してくれます。
これならいくらネストが増えても横長にならずに済んでみやすいですね.
params = { user: { info: { name: { first: :Suzuki } } } }
info = params.dig(:user, :info, :name, :first)
# => :Suzuki
info = params.dig(:user, :info, :full_name)
# => nil
参考: https://techracho.bpsinc.jp/hachi8833/2017_11_30/48336
digを使おう
ネストした値を取得するとき、
・ エラーを気にする必要がない。
・ 簡潔でワンライナーにしたときにみやすい
という点でみると、個人的にdig
が一番最適かなと思います。
params = { user: { info: { name: { first: :Suzuki } } } }
params[:user][:info][:name][:first] if params[:user] && params[:user][:info] && params[:user][:info][:name] && params[:user][:info][:name][:first]
# => :Suzuki
params[:user].try(:[], :info).try(:[], :name).try(:[], :first)
# => :Suzuki
params.dig(:user, :info, :name, :first)
# => :Suzuki
irb
とかで簡単に動作確認できるので、ぜひリファクタとかで使ってみてください!