Railsにおけるdigメソッドの活用方法
背景
発生したエラーを解消する過程で色々調べたため内容を書き留めておきたいと思いました。
本記事では、digメソッドに関する説明を簡単に解説していきます。
情報に関しては適当にフェイクを入れていますので、ご注意ください
digメソッド
digメソッドとは
指定したハッシュのキーを繰り返し指定することなく、深い階層の要素を取り出すことができるメソッドです。
例えば以下のようにネストされたハッシュがあるとします。
hash = { params: { user: { score: { "1": { "is_failed" => true, "score_id" => 5 } } } } }
上記のケースでis_failedの値を取り出したい場合は以下のようにdigメソッドにキーを引数に渡すことでvalueにアクセスすることができます。
# 実行 hash.dig(:params, :user, :score, :"1", "is_failed") # 結果 true
引数にシンボルを用いてハッシュのキーを指定することで深い階層にアクセスすることができます。 取り出したい要素のキーを指定することで必要な値を取り出すことができます。
digメソッドの利点 - キーが存在しない場合に例外が発生せずnilを返す
digメソッドは途中のオブジェクトが nil であった場合は nil を返します。
# 実行 (仮にscoreが存在していなかった場合) hash.dig(:params, :user, :score, :"1", "is_failed") # 結果 nil
例えば、先程の例でis_failedの値にアクセスしたい場合は以下のようにハッシュを辿る必要があります。
hash[:params][:user][:score]["1"][:is_failed]
このような場合、ハッシュのキーが存在しない場合にNoMethodError
などの例外が発生してしまいます。
digメソッドを使用すると、以下のように書くことができ、指定したキーが存在しない場合にnilが返るため、例外が発生する心配がありません。
hash.dig(:params, :user, :score, "1", :is_failed)
複雑なハッシュの構造に対して、コードを簡潔に書くこともできます。
hash.dig(:params, :user, :score)&.dig("1", :is_failed)
少し複雑になりがちなケースにも対応出来た
少しフェイク入れてますが、is_failedは他のテーブルのチェックボックスの入力フォームに対応するデータのため、入力時、更新時、バリデーション時でもチェックボックスがリセットされないように条件を絡める必要があります。
結構複雑な条件式になっています。
def set_test_details test_details = TestDetail.includes(:test).map do |test_detail| { id: test_detail.id, name: test_detail.name, test_name: test_detail.test.name, is_failed: params[:user].present? && params[:user][:score_detail_data].present? ? (params[:score][:score_detail_data][is_failed:] || false) : @score&score._details&.exists?(test_detail_id: test_detail.id, is_failed: true) || false } end end
paramsがnilだった場合にエラーが発生するため、present?や&&演算子を使って
またさらにparams[:user][:score_detail_data]がnilだった場合にエラーが発生するため、present?や演算子を使っています。
上記をdigメソッドを使用すると以下のようになります。
def set_test_details test_details = TestDetail.includes(:test).map do |test_detail| { id: test_detail.id, name: test_detail.name, test_name: test_detail.test.name, is_failed: params.dig(:user, :score_detail_data, "is_failed") || (@score.present? && @score.score_details.exists?(test_detail_id: test_detail.id, is_failed: true)) } end end
digを使うことで例外を阻止するための構文を書かずに済んで若干シンプルになりました。
修正点は以下の通りです。
- params.dig を使用して、深い階層のハッシュから値を取得するように修正
- パラメータ params[:score] を params[:user] に修正
- @score&score._details&.exists?(test_detail_id: test_detail.id, is_failed: true) || false を @score.present? && @score.score_details.exists?(test_detail_id: test_detail.id, is_failed: true) に修正
- &. でチェーンしていた箇所を、 present? で置き換え
- || false の部分は不要に
- &. でチェーンしていた箇所を、 present? で置き換え
eachを書く際にも使える
ここでハッシュの展開を行うのが良いか悪いかは別として、以下のように書くこともできます。
hash.dig(:params, :user, :score)&.each do |key, value| puts "score id: #{key}" puts "is failed: #{value['is_failed']}" end
digメソッドの戻り値がnilである場合に、eachメソッドが実行されないように&.演算子を使用しています。
digメソッドは、指定されたキーが存在しない場合にnilを返すため、&.演算子を使用して、nilチェックを行っています。
ハッシュが深かったり例外を阻止するための構文が必要になりがちなケースではdigメソッドは良いのかなと思いました。
おわりに
コメント
本記事の内容は以上になります!
参考になったり学びのきっかけになりますと幸いです。
間違いがありましたら修正いたしますので、ご指摘ください。
興味があれば他の記事も更新していきますので是非ご覧になってください♪
プログラミングスクールのご紹介 (卒業生より)
お世話になったプログラミングスクールであるRUNTEQです♪
こちらのリンクを経由すると1万円引きになります。
RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。
もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。