lib/axiom/algebra/extension.rb
# encoding: utf-8
module Axiom
module Algebra
# Extend a relation to include calculated attributes
class Extension < Relation
include Relation::Operation::Unary
include Equalizer.new(:operand, :extensions)
# The extensions for the relation
#
# @return [Hash]
#
# @api private
attr_reader :extensions
# Initialize an Extension
#
# @param [Relation] operand
# the relation to extend
# @param [Hash] extensions
# the extensions to add
#
# @return [undefined]
#
# @api private
def initialize(operand, extensions)
super(operand)
keys = extensions.keys
@header = @header.extend(keys)
@extensions = Hash[@header.project(keys).zip(extensions.values)]
end
# Iterate over each tuple in the set
#
# @example
# extension = Extension.new(operand, extensions)
# extension.each { |tuple| ... }
#
# @yield [tuple]
#
# @yieldparam [Tuple] tuple
# each tuple in the set
#
# @return [self]
#
# @api public
def each
return to_enum unless block_given?
extensions = self.extensions.values
operand.each { |operand_tuple| yield operand_tuple.extend(header, extensions) }
self
end
# Insert a relation into the Extension
#
# The other relation must be a matching extension.
#
# @example
# new_relation = extension.insert(other)
#
# @param [Relation] other
#
# @return [Extension]
#
# @raise [ExtensionMismatchError]
# raised when inserting with a mismatching extension
#
# @api public
def insert(other)
assert_matching_extensions(other, INSERTED)
operand.insert(other.operand).extend(extensions)
end
# Delete a relation from the Extension
#
# The other relation must be a matching extension.
#
# @example
# new_relation = extension.delete(other)
#
# @param [Relation] other
#
# @return [Extension]
#
# @raise [ExtensionMismatchError]
# raised when deleting with a mismatching extension
#
# @api public
def delete(other)
assert_matching_extensions(other, DELETED)
operand.delete(other.operand).extend(extensions)
end
private
# Assert that the other relation has matching extensions
#
# @param [Relation] other
#
# @param [String] event
#
# @return [undefined]
#
# @raise [ExtensionMismatchError]
# raised when inserting a relation does not have matching extensions
#
# @api private
def assert_matching_extensions(other, event)
return if other.kind_of?(self.class) && extensions.eql?(other.extensions)
fail ExtensionMismatchError, "other relation must have matching extensions to be #{event}"
end
module Methods
# Return an extended relation
#
# @example with extensions
# extension = relation.extend(extensions)
#
# @example with a context block
# extension = relation.extend do |context|
# context.add(:total, context[:unit_price] * context[:quantity])
# end
#
# @param [Array] args
# optional arguments
#
# @yield [function]
# Evaluate an extension function
#
# @yieldparam [Evaluator::Context] context
# the context to evaluate the function within
#
# @return [Extension]
#
# @api public
def extend(*args, &block)
Extension.new(self, coerce_to_extensions(*args, &block))
end
private
# Coerce the arguments and block into a extensions
#
# @param [#to_hash] extensions
# optional extensions with attribute keys and function/literal values
#
# @yield [function]
# Evaluate an extension function
#
# @yieldparam [Evaluator::Context] context
# the context to evaluate the function within
#
# @return [Extension]
#
# @api private
def coerce_to_extensions(extensions = Undefined, &block)
if extensions.equal?(Undefined)
header.context(&block).functions
else
extensions
end
end
end # module Methods
Relation.class_eval { include Methods }
end # class Extension
end # module Algebra
end # module Axiom