lib/axiom/tuple.rb

Summary

Maintainability
A
0 mins
Test Coverage
# encoding: utf-8

module Axiom

  # A set of objects representing a unique fact in a relation
  class Tuple
    extend Aliasable
    include Equalizer.new(:data)

    inheritable_alias(:[] => :fetch)

    # The tuple header
    #
    # @return [Header]
    #
    # @api private
    attr_reader :header

    # The tuple data
    #
    # @return [Hash]
    #
    # @api private
    attr_reader :data

    # Convert the Tuple into an Array
    #
    # @example
    #   array = tuple.to_ary
    #
    # @return [Array]
    #
    # @api public
    attr_reader :to_ary

    # Initialize a Tuple
    #
    # @param [Header] header
    #   the tuple header
    # @param [#to_ary] data
    #   the tuple data
    #
    # @return [undefined]
    #
    # @api private
    def initialize(header, data)
      @header = header
      @to_ary = self.class.freezer.call(data)
      @data   = Hash[header.zip(@to_ary)]
    end

    # Lookup a value in the tuple given an attribute
    #
    # @example
    #   value = tuple.call(attribute)
    #
    # @param [Attribute] attribute
    #
    # @return [Object]
    #
    # @api public
    def fetch(attribute)
      data.fetch(header.fetch(attribute))
    end

    # Return a tuple with only the specified attributes
    #
    # @example
    #   new_tuple = tuple.project(header)
    #
    # @param [Header] header
    #   the attributes to include in the tuple
    #
    # @return [Tuple]
    #
    # @api public
    def project(header)
      self.class.new(header, data.values_at(*header))
    end

    # Extend a tuple with function results
    #
    # @example
    #   new_tuple = tuple.extend(header, [func1, func2])
    #
    # @param [Header] header
    #   the attributes to include in the tuple
    # @param [Array<Object>] extensions
    #   the functions to extend the tuple with
    #
    # @return [Tuple]
    #
    # @api public
    def extend(header, extensions)
      join(
        header,
        extensions.map { |extension| Function.extract_value(extension, self) }
      )
    end

    # Rename a tuple to use a new header
    #
    # @example
    #   new_tuple = tuple.rename(header)
    #
    # @param [Header] header
    #
    # @return [Tuple]
    #
    # @api public
    def rename(header)
      self.class.new(header, to_ary)
    end

    # Append values to the tuple and return a new tuple
    #
    # @example
    #   new_tuple = tuple.join(header, [1, 2, 3])
    #
    # @param [Header] header
    #   the attributes to include in the tuple
    # @param [Array] values
    #   the values to append
    #
    # @return [Tuple]
    #
    # @api public
    def join(header, values)
      self.class.new(header, to_ary + values)
    end

    # Return the predicate matching the tuple
    #
    # @return [Function]
    #
    # @api private
    def predicate
      header.reduce(Function::Proposition::Tautology.instance) do |predicate, attribute|
        predicate.and(attribute.eq(attribute.call(self)))
      end
    end

    # Coerce the tuple into a Hash
    #
    # @example
    #   tuple.to_hash  # => data as a Hash
    #
    # @return [Hash{Symbol => Object}]
    #
    # @api public
    def to_hash
      Hash[data.map { |attribute, value| [attribute.name, value] }]
    end

    # The number of attributes
    #
    # @return [Integer]
    #
    # @api public
    def size
      header.size
    end

    # Display the tuple data in a human readable form
    #
    # @example
    #   tuple.inspect  # => data as a String
    #
    # @return [String]
    #
    # @api public
    def inspect
      to_hash.inspect
    end

  private

    # Coerce an Array-like object into a Tuple
    #
    # @param [Tuple, #to_ary] object
    #   the tuple or tuple data
    #
    # @return [Tuple]
    #
    # @api private
    def coerce(object)
      self.class.coerce(header, object)
    end

    # Coerce an Array-like object into a Tuple
    #
    # @param [Header] header
    #   the tuple header
    # @param [Tuple, #to_ary] object
    #   the tuple or tuple data
    #
    # @return [Tuple]
    #
    # @api private
    def self.coerce(header, object)
      if object.kind_of?(Tuple)
        object
      elsif !object.respond_to?(:to_ary)
        nil
      elsif header.size != object.to_ary.size
        object
      else
        new(header, object)
      end
    end

    memoize :predicate, :to_hash, :inspect

  end # class Tuple
end # module Axiom