markijbema/strict_struct

View on GitHub
lib/strict_struct.rb

Summary

Maintainability
A
0 mins
Test Coverage
require "strict_struct/version"

module StrictStruct
  module Helper
    def self.assert_keyword_error type, keywords
      if keywords.length == 1
        raise ArgumentError, "#{type} keyword: " + keywords.first.to_s
      elsif keywords.length > 1
        raise ArgumentError, "#{type} keywords: " + keywords.join(', ')
      end
    end

    def self.validate_arguments given, expected
      missing_arguments = expected.reject {|name| given.include? name }
      Helper.assert_keyword_error "missing", missing_arguments

      extra_arguments = given.reject {|name| expected.include? name }
      Helper.assert_keyword_error "unknown", extra_arguments
    end
  end

  class AttributeModule < Module
    def initialize *attributes
      super() do
        define_method :initialize do |hash={}|
          Helper.validate_arguments(hash.keys, attributes)
          @_strict_struct_hash = Hash[hash.to_a].freeze
        end

        attributes.each do |attribute|
          define_method(attribute) do
            @_strict_struct_hash[attribute]
          end
        end

        def to_h
          to_hash
        end

        def to_hash
          Hash[@_strict_struct_hash.to_a]
        end

        define_method :== do |other|
          return false unless self.class == other.class

          attributes.all? {|name| self.send(name) == other.send(name)}
        end

        define_method :hash do
          @_strict_struct_hash.hash
        end
      end
    end
  end

  def self.new(*attributes, &block)
    Class.new do
      include AttributeModule.new(*attributes)
      class_eval(&block) if block_given?
    end
  end
end