【Ruby】digメソッドを使用して、ネストされた配列とハッシュの値を安全に取り出す方法

はじめに

こんにちは株式会社TOKOSのナオキです!
今回はRubyのdigメソッドについて解説をしていきます。

この記事では以下の内容について取り上げています。

  • digメソッドの概要
  • digメソッドの基本的な使用方法
  • mapcompactメソッドと組み合わせた使用方法
  • filter_mapメソッドの使用方法

対象読者

  • Ruby初学者の方
  • ネストされた配列やハッシュで冗長なnilチェックを減らしたいと思ってる方

digメソッドの概要

digメソッドはネストされた配列やハッシュの値を安全に取得することができます。
安全に取得とは、本来存在しない要素にアクセスをするとエラーが発生します。
digメソッドを使用した場合、存在しない要素にアクセスしてもエラーが発生せず、nilが返されます。
複雑なデータ構造を扱う際に、冗長なnilチェックを記述しないでスッキリとしたコードを記述することが出来ます。

以前開発をしていた時、とあるAPIの情報を扱っていました。
その際に取得するデータが複雑にネストされたデータ構造をしていて、下記のような冗長なnilチェックを記述していました。

Ruby
data = {
  user: {
    profile: {
      address: {
        city: 'Tokyo'
      }
    }
  }
}

# 冗長なnilチェック
if data[:user] && data[:user][:profile] && data[:user][:profile][:address]
  city = data[:user][:profile][:address][:city]
else
  city = nil
end

上記のような冗長なコードは可読性が良くありません。
digメソッドを使用したら冗長なnilチェックを記述する必要はなく、下記のようにスッキリとしたコードを記述することができます!

Ruby
city = data.dig(:user, :profile, :address, :city)

基本的な使用方法

基本的な構文は下記のようになります。

ハッシュの基本構文

Ruby
data.dig(:key1, :key2, :key3, ...)

配列の基本構文

Ruby
data.dig(index1, index2, index3, ...)

digメソッドは与えられたキーやインデックスを順番にたどり、キーやインデックス存在しない場合や途中でnilに遭遇した場合はnilを返します。

基本的な使用例

ハッシュの場合

Ruby
data = {
  user: {
    profile: {
      name: 'Taro',
      address: {
        city: 'Tokyo'
      }
    }
  }
}

name = data.dig(:user, :profile, :name)
puts name # => 'Taro'

city = data.dig(:user, :profile, :address, :city)
puts city # => 'Tokyo'

# 存在しないキーにアクセスしようとした場合nilが返る
postal_code = data.dig(:user, :profile, :address, :postal_code)
puts postal_code # => nil

配列の場合

Ruby
animals = [['dog', 'cat',['horse', 'bird']]]

animal1 = animals.dig(0, 0)
puts animal1 # => 'dog'

animal2 = animals.dig(0, 2, 1)
puts animal2 # => 'bird'

# 存在しないインデックスにアクセスしようとした場合nilが返る
animal3 = animals.dig(0, 3)
puts animal3 # => nil

ハッシュと配列が混在したデータ構造の場合

Ruby
data = {
  users: [
    { name: 'Naoki', details: { age: 30 } },
    { name: 'Kenta', details: { age: 25 } }
  ]
}

# Naokiの年齢を取得
age1 = data.dig(:users, 0, :details, :age)
puts age1 # => 30

# 存在しないデータへのアクセス
age2 = data.dig(:users, 2, :details, :age)
puts age2 # => nil

map、compactメソッドと組み合わせた使用方法

digメソッドとmapcompactメソッドを一緒に使用した応用を紹介します。
実用的だと思うのでぜひ参考にしてみてください!

Ruby
users = [
  { id: 1, profile: { name: 'Naoki', age: 30 } },
  { id: 2, profile: { name: 'Kenta' } },
  { id: 3, profile: { age: 20 } },
  { id: 4, profile: nil }
]

names = users.map do |user|
  user.dig(:profile, :name)
end.compact
puts names.inspect # => ['Naoki', 'Kenta'] 

mapメソッドは、配列の各要素に対して処理を行い、その結果を新たな配列として返します。
compactメソッドは配列内のnilの要素を取り除きます。
上記の2つをdigメソッドと組み合わせることで、users配列の中で存在するnameとnilがnames配列に格納されます。
そしてnilの要素はcompactによって取り除かれるので結果として、存在するnameのみがnames配列に格納されることになります。

このように他のメソッドと組み合わせて使用することで、スッキリとした可読性の良いコードを記述することが出来ます!

ナオキ
ナオキ

map, compactと組み合わせた使い方を紹介しましたが、実は同じ処理をもっと簡潔に記述することが出来るメソッドがあります!

filter_mapメソッドの使用方法

上記で紹介したmap, compactメソッドを組み合わせた処理をfilter_mapメソッドのみで実現できます!
使用例は下記のコードになります。

Ruby
users = [
  { id: 1, profile: { name: 'Naoki', age: 30 } },
  { id: 2, profile: { name: 'Kenta' } },
  { id: 3, profile: { age: 20 } },
  { id: 4, profile: nil }
]

names = users.filter_map do |user|
  user.dig(:profile, :name)
end
puts names.inspect # => ['Naoki', 'Kenta'] 

usersに対してfilter_mapメソッドを使用することで、ブロック内の評価が真(true)であるものだけを返します。
nilfalseになるので取り除かれます。
上記のように記述することでより簡潔に可読性の良いコードを記述することが出来ます!

さいごに

今回はRubyのdigメソッドについて解説しました。
開発を行う中で、複雑にネストされたデータを扱うときも少なからずあるとおもいます。
そんなときdigメソッドを知っているとコードをより簡潔に記述することができ、可読性を良くすることができます。
なのでネストされたデータを扱う時は積極的に使用してみてください!