module Structure def self.included(base) base .extend(ClassMethods) .instance_variable_set(:@attribute_names, []) end def attributes self.class.attribute_names.reduce({}) { |ret, name| ret.update(name => self.send(name)) } end def ==(other) attributes == other.attributes end def inspect "#<#{self.class} #{ attributes .map { |key, val| if val.is_a?(Array) && val.size > 3 "#{key}=[#{val.take(3).map(&:inspect).join(', ')}...]" else "#{key}=#{val.inspect}" end } .join(', ') }>" end alias_method :to_h, :attributes alias_method :eql?, :== alias_method :to_s, :inspect module ClassMethods attr_reader :attribute_names def to_struct return Struct.const_get(name, false) if Struct.const_defined?(name, false) Struct.new(name, *attribute_names) do def initialize(data = {}) data.each { |key, val| self.send("#{key}=", val) } end end end def inherited(subclass) subclass.instance_variable_set(:@attribute_names, @attribute_names.dup) end def attribute(name, &blk) define_method("_#{name}", blk) private "_#{name}" module_eval <<-END def #{name} @#{name} ||= _#{name} end END @attribute_names << name end end end