Last active
March 29, 2019 09:35
-
-
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
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
# 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 |
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" | |
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