Created
March 13, 2013 06:21
-
-
Save tatey/5149759 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# RecursiveClosedStruct is similar to OpenStruct, except: | |
# | |
# * Immutable. Cannot be changed after instantiation. | |
# * Raises NoMethodError on undefined methods instead of returning nil. | |
# * Recursively coerces descendants into a RecursiveClosedStruct. | |
# | |
# NoMethodError example: | |
# struct = RecursiveClosedStruct.new(key1: "value") | |
# struct.key1 # => "value" | |
# struct.key2 # => NoMethodError | |
# | |
# Respond to example: | |
# struct = RecursiveClosedStruct.new(key: "value") | |
# struct.respond_to?(:key) # => true | |
# | |
# Recursive example: | |
# struct = RecursiveClosedStruct.new(one: two: {[{three: "four"]}) | |
# struct.one.two[0].three # => "four" | |
class RecursiveClosedStruct | |
def initialize(hash) | |
@hash = hash | |
end | |
def values | |
@hash.values | |
end | |
private | |
def fetch(name) | |
@hash.fetch(name) do | |
raise NoMethodError.new("undefined method `#{name}' for #{self}") | |
end | |
end | |
def method_missing(name, *) | |
object = fetch(name) | |
if object.is_a?(Hash) | |
self.class.new(object) | |
elsif object.is_a?(Array) | |
object.map do |value| | |
self.class.new(value) | |
end | |
else | |
object | |
end | |
end | |
def respond_to_missing?(name, *) | |
@hash.keys.include?(name) || super | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
describe RecursiveClosedStruct do | |
it 'provides readers from a hash' do | |
struct = RecursiveClosedStruct.new(key: 'value') | |
struct.key.should eq('value') | |
end | |
it 'provides respond_to? from a hash' do | |
struct = RecursiveClosedStruct.new(key: 'value') | |
struct.respond_to?(:key).should be_true | |
end | |
it 'raises when there is no such key' do | |
struct = RecursiveClosedStruct.new(key: 'value') | |
-> { struct.other_key }.should raise_error(NoMethodError) | |
end | |
it 'recurses through array of hashes' do | |
struct = RecursiveClosedStruct.new({ | |
one: [{two: 'three'}] | |
}) | |
struct.one[0].two.should eq('three') | |
end | |
it 'recurses through hashes' do | |
struct = RecursiveClosedStruct.new({ | |
one: {two: {three: 'four'} } | |
}) | |
struct.one.two.three.should eq('four') | |
end | |
it 'has values' do | |
struct = RecursiveClosedStruct.new({one: 'two', three: 'four'}) | |
struct.values.should eq(['two', 'four']) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment