Skip to content

Instantly share code, notes, and snippets.

@tatey
Created March 13, 2013 06:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tatey/5149759 to your computer and use it in GitHub Desktop.
Save tatey/5149759 to your computer and use it in GitHub Desktop.
# 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
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