Skip to content

Instantly share code, notes, and snippets.

@whylom
Last active August 29, 2015 13:57
Show Gist options
  • Save whylom/9839785 to your computer and use it in GitHub Desktop.
Save whylom/9839785 to your computer and use it in GitHub Desktop.
Superhash!

Superhash!

A wrapper for Hash that lets you access keys via dynamic methods (like Ruby's OpenStruct) but also:

  • works recursively
  • also lets you use Hash methods like [] and keys

Example

hash = Superhash.new({ foo: "bar", other: { "fizz" => "buzz" } })

hash.foo #=> "bar"
hash.other.fizz #=> "buzz"

hash[:foo] #=> "bar"
hash["foo"] #=> "bar"
hash.keys #=> ["foo", "other"]

hash.other #=> #<Superhash:0x007fd841848c28>
hash.other.hash #=> {"fizz"=>"buzz"}
class Superhash
attr_reader :hash
def initialize(hash)
@hash = hash
end
def [](key)
hash[key.to_s] || hash[key.to_sym]
end
def method_missing(method, *args, &block)
if value = hash[method] || hash[method.to_s]
value.is_a?(Hash) ? self.class.new(value) : value
elsif hash.respond_to? method
hash.send(method, *args, &block)
else
super # raise a NoMethodError
end
end
end
describe Superhash do
subject do
Superhash.new({
:foo => "bar",
"fizz" => "buzz",
:subhash => { tick: "tock" }
})
end
context "when the original key is a symbol" do
it "can be accessed as a method" do
expect(subject.foo).to eq("bar")
end
it "can be accessed with [] as a symbol or as a string" do
expect( subject[:foo] ).to eq("bar")
expect( subject["foo"] ).to eq("bar")
end
end
context "when the original key is a string" do
it "can be accessed as a method" do
expect(subject.fizz).to eq("buzz")
end
it "can be accessed with [] as a symbol or as a string" do
expect( subject[:fizz] ).to eq("buzz")
expect( subject["fizz"] ).to eq("buzz")
end
end
describe "a nested hash" do
it "can be accessed as a Superhash" do
expect(subject.subhash).to be_a(Superhash)
expect(subject.subhash.tick).to eql("tock")
end
end
context "when calling a method defined on Hash" do
it "delegates to the original hash" do
expect(subject.size).to eql(3)
expect(
subject.fetch(:using_arguments) { "using blocks" }
).to eql("using blocks")
end
end
context "when calling a method NOT defined on Hash" do
it "raises a NoMethodError" do
expect { subject.fake_method }.to raise_error(NoMethodError)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment