Rails
params

paramsでネストした値を受け取るときにnilエラーを回避する方法


一つ目のキーが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とかで簡単に動作確認できるので、ぜひリファクタとかで使ってみてください!