【Ruby】OpenStructを使ってオンデマンドにアクセッサーを生やす方法

# はじめに

Ruby標準のOpenStructクラスを使うことで手軽にデータ構造を扱うことができます。
この記事ではOpenStructを使ったサンプルを紹介したいと思います。

# これらに興味がある人向けの記事です

- OpenStructを使ってみたい
- データ構造はハッシュよりクラスにしたい
- クラスを宣言的に用意するのは面倒

# こんな人には不向きです

- ハッシュデータ構造をそのまま扱うのに何の苦も感じない
- クラスは宣言的に用意したい

# 目次

- [OpenStructとは](#openstructとは)
- [サンプルデータ構造](#サンプルデータ構造)
- [OpenStructを使うサンプル](#openstructを使うサンプル)
- [最後に](#最後に)

# OpenStructとは

Ruby標準のライブラリです。リファレンスによると

```
要素を動的に追加・削除できる手軽な構造体を提供するクラスです。

OpenStruct のインスタンスに対して未定義なメソッド x= を呼ぶと、 
OpenStruct クラスの BasicObject#method_missing で捕捉され、
そのインスタンスにインスタンスメソッド x, x= が定義されます。
この挙動によって要素を動的に変更できる構造体として働きます。
```

# サンプルデータ構造

例えば、次のような映画のデータベースみたいなものがあったとします。

```
映画作品集
	映画
		タイトル
		評価
		監督
			名前
			生年月日
		キャスト
			俳優
				名前
				生年月日
			俳優
				：
				：
	映画
		：
		：
```

このデータ構造を次のようなハッシュと配列で表現したとします。


```ruby:sample_data.rb
$sample_data = {
  :movies => [
    {
      :title => "インターステラー",
      :star => "8.6",
      :director => {:name => "クリストファーノーラン", :born => "1970-07-30"},
      :cast => [
        {:name => "マシューマコノヒー", :born => "1969-11-04"},
        {:name => "アンハサウェイ", :born => "1982-11-12"}
      ]
    },
    {
      :title => "パルプフィクション",
      :star => "8.9",
      :director => {:name => "クエンティンタランティーノ", :born => "1963-03-27"},
      :cast => [
          {:name => "ジョントラボルタ", :born => "1954-02-18"},
          {:name => "サミュエル L ジャクソン", :born => "1948-12-21"},
          {:name => "ブルースウィリス", :born => "1955-03-19"}
      ]
    },
    {
      :title => "君の名は",
      :star => "8.3",
      :director => {:name => "新海誠", :born => "1973-02-09"},
      :cast => [
        {:name => "神木隆之介", :born => "1993-05-19"},
        {:name => "上白石萌音", :born => "1998-01-27"},
      ]
    }
  ]
}
```

# OpenStructを使うサンプル

OpenStructの初期化メソッドにハッシュを渡して、アクセッサーを使おうとするとハッシュキーに対応するアクセッサーが自動で定義されます。

ただし、ネストしたハッシュや配列の中身まで一発でアクセッサーを作ることができないようなので階層構造に沿ってOpenStructを生成しています。

```ruby: sample1.rb
require_relative 'sample_data'
require 'ostruct'

#
# サンプルデータをOpenStructにマッピングする
#

root = OpenStruct.new
root.movies = []
$sample_data[:movies].each do | movie_hash |
  movie = OpenStruct.new(:title => movie_hash[:title], :star => movie_hash[:star])
  director_hash = movie_hash[:director]
  director = OpenStruct.new(:name => director_hash[:name], :born => director_hash[:born])
  movie.director = director

  cast = movie_hash[:cast]
  movie.cast = []
  cast.each do | actor_hash |
    actor = OpenStruct.new(:name => actor_hash[:name], :born => actor_hash[:born])
    movie.cast.push( actor )
  end
  root.movies.push( movie )
end

#
# マッピングされたデータ構造を操作する
#

root.movies.each do | movie |
  puts "movie: title: #{movie.title}, star: #{movie.star}"
  puts "director: name: #{movie.director.name}, born: #{movie.director.born}"
  movie.cast.each do |actor|
    puts "actor: name: #{actor.name}, born: #{actor.born}"
  end
  puts
end
```


```plaintext:実行結果
movie: title: インターステラー, star: 8.6
director: name: クリストファーノーラン, born: 1970-07-30
actor: name: マシューマコノヒー, born: 1969-11-04
actor: name: アンハサウェイ, born: 1982-11-12

movie: title: パルプフィクション, star: 8.9
director: name: クエンティンタランティーノ, born: 1963-03-27
actor: name: ジョントラボルタ, born: 1954-02-18
actor: name: サミュエル L ジャクソン, born: 1948-12-21
actor: name: ブルースウィリス, born: 1955-03-19

movie: title: 君の名は, star: 8.3
director: name: 新海誠, born: 1973-02-09
actor: name: 神木隆之介, born: 1993-05-19
actor: name: 上白石萌音, born: 1998-01-27
```

# 最後に

ネストしたハッシュや配列を一発で変換できるやり方を知ってる方いらっしゃったら教えてください。
（JSON.parseは出来る）