ハッシュを渡すと構造体を返してくれるOpenStructが便利で使い始めたのですが、ネストしたハッシュに対して再帰的(recursive)には実行されないよう(オプションとかも無さそう)なので、この辺を参考にさせていただき「DeepOpenStruct」を書いてみました。
Code
lib/deep_open_struct.rb
class DeepOpenStruct < OpenStruct
def initialize(hash = nil)
@table = {}
@hash_table = {}
if hash
hash.each do |key, value|
@table[key.to_sym] =
if value.is_a?(Array)
value.map { |_value| (_value.is_a?(Array) || _value.is_a?(Hash)) ? self.class.new(_value) : _value }
elsif value.is_a?(Hash)
self.class.new(value)
else
value
end
@hash_table[key.to_sym] = value
new_ostruct_member(key)
end
end
end
def to_h
@hash_table
end
end
RSpec
spec/libs/deep_open_struct_spec.rb
RSpec.describe DeepOpenStruct, type: :lib do
let!(:strict) {
DeepOpenStruct.new(
foo: "abc",
bar: %w(hoge, fuga, piyo),
baz: {
foobar: {
hoge: :hoge,
fuga: nil,
piyo: [
123,
456,
{
z: {
abc: "def",
def: 999,
ghi: [{ xyz: "xyz" }]
}
}
]
}
},
qux: []
)
}
it { expect(strict.foo).to eq "abc" }
it { expect(strict.bar).to eq %w(hoge, fuga, piyo) }
it { expect(strict.baz.foobar.hoge).to eq :hoge }
it { expect(strict.baz.foobar.fuga).to be_nil }
it { expect(strict.baz.foobar.piyo.size).to eq 3 }
it { expect(strict.baz.foobar.piyo.first).to eq 123 }
it { expect(strict.baz.foobar.piyo.second).to eq 456 }
it { expect(strict.baz.foobar.piyo.third.z.abc).to eq "def" }
it { expect(strict.baz.foobar.piyo.third.z.def).to eq 999 }
it { expect(strict.baz.foobar.piyo.third.z.ghi.size).to eq 1 }
it { expect(strict.baz.foobar.piyo.third.z.ghi.first.xyz).to eq "xyz" }
it { expect(strict.qux).to eq [] }
end
補足
recursive-open-structというGemを使ったり、JSONに変換してから渡したりと、他にもやりようはありそうです。