class Yarrow::Schema::Value

Value object (with comparison by value equality). This just chucks back a Ruby struct but wraps the constructor with method advice that handles type checking and conversion.

Public Class Methods

factory(*slots, **fields, &block) click to toggle source
Calls superclass method
# File lib/yarrow/schema/value.rb, line 27
def self.factory(*slots, **fields, &block)
  if slots.empty? && fields.empty?
    raise ArgumentError.new("missing attribute definition")
  end

  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, :any]}]]
  end

  validator = Dictionary.new(fields_spec)

  struct = ValueType.new(*slots_spec, keyword_init: true, &block)

  struct.define_method :initialize do |*args, **kwargs|
    attr_values = if args.any?
      raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
      if args.first.instance_of?(Hash) and args.count == 1
        args.first
      else
        Hash[slots.zip(args)]
      end
    else
      kwargs
    end

    converted_values = validator.cast(attr_values)

    super(**converted_values)

    freeze
  end

  struct.define_method(:merge) do |other|
    unless other.is_a?(self.class)
      raise ArgumentError.new("cannot merge incompatible values")
    end

    kwargs = validator.attr_names.reduce({}) do |data, attr_name|
      current_val = self.send(attr_name)
      other_val = other.send(attr_name)

      # TODO: handle optional types better
      # call out to dictionary?
      # validator.merge(attr_name, current_val, other_val)
      data[attr_name] = if current_val.nil?
        other_val
      elsif other_val.nil?
        current_val
      else
        other_val
      end

      data
    end

    struct.new(**kwargs)
  end

  struct
end
new(*slots, **fields, &block) click to toggle source
# File lib/yarrow/schema/value.rb, line 23
def self.new(*slots, **fields, &block)
  factory(*slots, **fields, &block)
end