class Mementus::Model::Value

Value object with comparison by value equality.

This is a wrapper class, used as a factory for generating instances of Ruby structs that wraps the Struct constructor with method advice to handle validation (and eventually type coercion if needed).

Public Class Methods

define(*slots, **fields, &block) click to toggle source
Calls superclass method
# File lib/mementus/model/value.rb, line 13
def self.define(*slots, **fields, &block)
  # Cannot define a Value without specifying slots or fields
  if slots.empty? && fields.empty?
    raise ArgumentError.new("missing attribute definition")
  end

  # Build and validate an attribute specification matching the stdlib
  # constructor convention
  slots_spec, fields_spec = if fields.any?
    raise ArgumentError.new("cannot use slots when field map is supplied") if slots.any?
    [fields.keys, fields]
  else
    [slots, Hash[slots.map { |s| [s, Type::Any]}]]
  end

  validator = Validator.new(fields_spec)

  # Define a new struct with the given specification of attributes
  struct = Struct.new(*slots_spec, keyword_init: true, &block)

  # Method advice wrapping the stdlib constructor
  struct.define_method :initialize do |*args, **kwargs|

    # Handles the original stdlib API of slot-based args if you really
    # must do things this way. The keyword args API is the preferred
    # approach.
    attr_values = if args.any?
      raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
      Hash[slots.zip(args)]
    else
      kwargs
    end

    # Check that the provided values match the defined attributes and types
    validator.check(attr_values)

    # TODO: type coercion or mapping decision goes here

    super(**attr_values)

    # Freeze the instance before returning to caller
    # TODO: if immutability becomes optional this needs to be wrapped
    freeze
  end

  struct
end
new(*slots, **fields, &block) click to toggle source
# File lib/mementus/model/value.rb, line 9
def self.new(*slots, **fields, &block)
  define(*slots, **fields, &block)
end