Skip to content

Instantly share code, notes, and snippets.

@henrik
Last active March 29, 2019 09:35
Show Gist options
  • Save henrik/19c68b2a41ab4d098ce8 to your computer and use it in GitHub Desktop.
Save henrik/19c68b2a41ab4d098ce8 to your computer and use it in GitHub Desktop.
HalfOpenStruct for #ruby. Like OpenStruct but doesn't let you read a non-assigned value (raises instead of returning nil). Also see my RecursiveClosedStruct: https://gist.github.com/henrik/5098550
# Like OpenStruct but doesn't let you read a non-assigned value (raises instead of returning nil).
# This avoids issues where you read the wrong value due to a typo and don't notice.
class HalfOpenStruct
def initialize(hash = {})
@hash = hash
end
def include?(name)
@hash.include?(name)
end
def fetch(name, fallback)
@hash.fetch(name, fallback)
end
def method_missing(name, *args)
if name.to_s.ends_with?("=")
write(name.to_s.chop.to_sym, *args)
elsif args.length == 0
read_or_raise(name)
else
raise NoMethodError
end
end
private
def write(name, value)
@hash[name] = value
end
def read_or_raise(name)
@hash.fetch(name) { raise "Unknown key: #{name}" }
end
end
require "spec_helper"
require "half_open_struct"
describe HalfOpenStruct do
it "can be initialized with a hash, and values read and written" do
s = HalfOpenStruct.new(name: "Joe")
expect(s.name).to eq "Joe"
s.age = 42
expect(s.age).to eq 42
end
it "raises trying to read an unknown value" do
s = HalfOpenStruct.new
expect { s.age }.to raise_error("Unknown key: age")
s.age = 42
expect(s.age).to eq 42
end
it "raises for unknown methods" do
s = HalfOpenStruct.new
expect { s.foo("bar") }.to raise_error(NoMethodError)
end
# Considered an API like "s.age?" but that's ambiguous with undefined vs. falsy values.
it "lets you query for values being defined" do
s = HalfOpenStruct.new
expect(s.include?(:age)).to be_false
s.age = 42
expect(s.include?(:age)).to be_true
end
it "lets you fetch values with a fallback" do
s = HalfOpenStruct.new
expect(s.fetch(:age, :fallback)).to eq :fallback
s.age = 42
expect(s.fetch(:age, :fallback)).to eq 42
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment