class BloodContracts::Core::Tuple

Meta refinement type, represents product of several refinement types

Attributes

attributes[R]

List of types in the Tuple

@return [Array<Refined>]

failure_klass[RW]

Accessor to define alternative to ContractFailure for failure method to use

@return [ContractFailure]

names[R]

Names of attributes

@return [Array<Symbol>]

values[R]

List of values in Tuple

@return [Array<Object>]

Public Class Methods

attribute(name, type = nil, &block) click to toggle source

Helper which registers attribute in the Tuple, also defines a reader

# File lib/blood_contracts/core/tuple.rb, line 43
def attribute(name, type = nil, &block)
  if type.nil?
    type = type_from_block(name, &block)
  else
    raise ArgumentError unless type < Refined
  end

  @attributes << type
  @names << name

  define_method(name) do
    match.context.dig(:attributes, name)
  end
end
inherited(new_klass) click to toggle source
# File lib/blood_contracts/core/tuple.rb, line 64
def inherited(new_klass)
  new_klass.instance_variable_set(:@attributes, [])
  new_klass.instance_variable_set(:@names, [])
  new_klass.instance_variable_set(:@finalized, true)
  new_klass.failure_klass ||= TupleContractFailure
  super
end
inspect() click to toggle source
Calls superclass method
# File lib/blood_contracts/core/tuple.rb, line 33
def inspect; super; end
new(*args, **kwargs, &block) click to toggle source

Metaprogramming around constructor Turns input into Tuple meta-class

@param (see initialze)

rubocop:disable Style/SingleLineMethods

Calls superclass method BloodContracts::Core::Refined::new
# File lib/blood_contracts/core/tuple.rb, line 26
def new(*args, **kwargs, &block)
  args = lookup_hash_args(*args, kwargs)

  return super(*args, **kwargs) if @finalized
  names = args.pop.delete(:names) if args.last.is_a?(Hash)

  raise ArgumentError unless args.all? { |type| type < Refined }
  tuple = Class.new(self) { def inspect; super; end }
  tuple.instance_variable_set(:@attributes, args)
  tuple.instance_variable_set(:@names, names.to_a)
  tuple.instance_variable_set(:@finalized, true)
  tuple.class_eval(&block) if block_given?
  tuple
end
new(*values, context: {}, **) click to toggle source

Tuple constructor, builds Tuple from list of data values

@param [Array<Object>] *values that we'll keep inside the Tuple @option [Hash<Symbol, Object>] context to share between types

# File lib/blood_contracts/core/tuple.rb, line 125
def initialize(*values, context: {}, **)
  @context = context
  @context[:attributes] ||= {}

  additional_context = values.last if values.last.is_a?(Hash)
  additional_context ||= {}

  @values = parse_values_from_context(context.merge(additional_context))
  @values ||= values

  @errors = []
end

Private Class Methods

lookup_hash_args(*args, **kwargs) click to toggle source

Handle arguments passed as hash with string or symbol keys

@param [Array<Object>] *args that can contain an array of arguments or a single Hash we're looking for

@param [Hash<Object, Object>] **kwargs hash that can be converted to args when args array is empty

@return [Array<Object>]

# File lib/blood_contracts/core/tuple.rb, line 103
def lookup_hash_args(*args, **kwargs)
  if args.empty?
    [kwargs]
  elsif args.length == 1 && args.last.is_a?(Hash)
    [args.first.symbolize_keys]
  else
    args
  end
end
type_from_block(name, &block) click to toggle source

Generate an anonymous type from a block

@param String name of the attribute to define the type for

@param Proc block which will be evaluated in a context of our anonymous type

@return Class

# File lib/blood_contracts/core/tuple.rb, line 83
def type_from_block(name, &block)
  raise ArgumentError unless block_given?

  const_set(
    "InlineType_#{name.to_s.capitalize}",
    Class.new(Refined) { class_eval(&block) }
  )
end

Public Instance Methods

attribute_errors() click to toggle source

Subset of attributes which are invalid

@return [Hash<String, ContractFailure>]

# File lib/blood_contracts/core/tuple.rb, line 191
def attribute_errors
  {}
end
attributes() click to toggle source

Hash of attributes (name & type pairs)

@return [Hash<String, Refined>]

# File lib/blood_contracts/core/tuple.rb, line 183
def attributes
  @context[:attributes]
end
mapped() click to toggle source

Turns match into array of unpacked values

@return [Array<Object>]

# File lib/blood_contracts/core/tuple.rb, line 158
def mapped
  @matches.map(&:unpack)
end
match() click to toggle source

The type which is the result of data matching process For Tuple it verifies that all the attributes data are valid types

@return [BC::Refined]

# File lib/blood_contracts/core/tuple.rb, line 143
def match
  @matches = attributes_enumerator.map do |(type, value), index|
    attribute_name = self.class.names[index]
    attributes.store(
      attribute_name, type.match(value, context: @context.dup)
    )
  end
  return if (failures = @matches.select(&:invalid?)).empty?
  failures.unshift(failure).reduce(:merge!)
end
to_h()
Alias for: unpack_h
to_hash()
Alias for: unpack_h
unpack_attributes()
Alias for: unpack_h
unpack_h() click to toggle source

Unpacked value in form of a hash per attribute

@return [Hash<String, ContractFailure>]

# File lib/blood_contracts/core/tuple.rb, line 172
def unpack_h
  @unpack_h ||= attributes.transform_values(&:unpack)
end
Also aliased as: to_hash, to_h, unpack_attributes

Private Instance Methods

attributes_enumerator() click to toggle source

@private

# File lib/blood_contracts/core/tuple.rb, line 211
        def attributes_enumerator
  self.class.attributes.zip(@values).each.with_index
end
failure(*) click to toggle source

Helper to build a ContractFailure with shared context

@return [ContractFailure]

# File lib/blood_contracts/core/tuple.rb, line 199
        def failure(*)
  self.class.failure_klass.new(context: @context)
end
inspect() click to toggle source

@private

# File lib/blood_contracts/core/tuple.rb, line 225
        def inspect
  "#<tuple #{self.class.name} of (#{values_by_names.join(', ')})>"
end
parse_values_from_context(context) click to toggle source

@private

# File lib/blood_contracts/core/tuple.rb, line 204
        def parse_values_from_context(context)
  return if context.empty?
  return unless (self.class.names - context.keys).empty?
  context.values_at(*self.class.names)
end
values_by_names() click to toggle source

@private

# File lib/blood_contracts/core/tuple.rb, line 216
        def values_by_names
  if self.class.names.empty?
    values
  else
    self.class.names.zip(values).map { |k, v| [k, v].join("=") }
  end
end