Skip to content

Instantly share code, notes, and snippets.

@thiagoa
Last active December 22, 2017 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thiagoa/4a38d4b5c57427e0f459ed83968b86b3 to your computer and use it in GitHub Desktop.
Save thiagoa/4a38d4b5c57427e0f459ed83968b86b3 to your computer and use it in GitHub Desktop.
class HStruct
def self.new(*members, defaults: {}, &block)
invalid_defaults = defaults.keys - members
fail "Invalid defaults #{invalid_defaults}" if invalid_defaults.any?
klass = Class.new do
@members = members
@defaults = defaults
class << self
attr_reader :members, :defaults
end
attr_reader(*members)
def initialize(attrs = {})
invalid_members = attrs.keys - self.class.members
fail "No members #{invalid_members} in HStruct" if invalid_members.any?
self.class.members.each do |key|
value = attrs[key] || self.class.defaults[key]
instance_variable_set :"@#{key}", value
end
end
def ==(other)
self.class == other.class && to_h == other.to_h
end
alias_method :eql?, :==
def to_h
self.class.members.map { |key| [key, public_send(key)] }.to_h
end
def each(&block)
return to_enum(__callee__) unless block_given?
to_h.each(&block)
end
def inspect
contents = self.class.members
.map { |key| %{#{key}="#{public_send(key).to_s.gsub('"', '\"')}"} }
.join(' ')
"#<hstruct #{self.class.name} #{contents}>"
end
end
klass.class_eval(&block) if block_given?
klass
end
end
# Usage
MyStruct = HStruct.new(:foo, :bar, :baz, :bat, defaults: { baz: 3 }) do
def batz
4
end
end
data_bag = MyStruct.new(foo: 1, bar: 2)
data_bag.foo #=> 1
data_bag.bar #=> 2
data_bag.baz #=> 3
data_bag.bat #=> nil
data_bag.batz #=> 4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment