lib/axiom/algebra/restriction.rb

Summary

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

module Axiom
  module Algebra

    # Restrict the tuples to those that match a predicate
    class Restriction < Relation
      include Relation::Operation::Unary
      include Equalizer.new(:operand, :predicate)

      TAUTOLOGY = Function::Proposition::Tautology.instance

      # The predicate for the relation
      #
      # @return [Function, #call]
      #
      # @api private
      attr_reader :predicate

      # Initialize a Restriction
      #
      # @param [Relation] operand
      #   the relation to restrict
      # @param [Function, #call] predicate
      #   the function to restrict the tuples with
      #
      # @return [undefined]
      #
      # @api private
      def initialize(operand, predicate)
        super(operand)
        @predicate = predicate
      end

      # Iterate over each tuple in the set
      #
      # @example
      #   restriction = Restriction.new(operand, predicate)
      #   restriction.each { |tuple| ... }
      #
      # @yield [tuple]
      #
      # @yieldparam [Tuple] tuple
      #   each tuple in the set
      #
      # @return [self]
      #
      # @api public
      def each
        return to_enum unless block_given?
        operand.each do |tuple|
          yield tuple if Function.extract_value(predicate, tuple).equal?(true)
        end
        self
      end

      # Insert a relation into the Restriction
      #
      # The tuples must match the predicate to be inserted.
      #
      # @example
      #   new_relation = restriction.insert(other)
      #
      # @param [Relation] other
      #
      # @return [Restriction]
      #
      # @api public
      def insert(other)
        other = coerce(other)
        operand.insert(other.restrict(predicate)).restrict(predicate)
      end

      # Delete a relation from the Restriction
      #
      # The tuples must match the predicate to be deleted.
      #
      # @example
      #   new_relation = restriction.delete(other)
      #
      # @param [Relation] other
      #
      # @return [Restriction]
      #
      # @api public
      def delete(other)
        other = coerce(other)
        operand.delete(other.restrict(predicate)).restrict(predicate)
      end

      module Methods

        # Return a relation with restricted tuples
        #
        # @example restriction with a predicate
        #  restriction = relation.restrict(predicate)
        #
        # @example restriction using a block
        #   restriction = relation.restrict do |context|
        #     context.a.eq('other').and(context.b.gte(42))
        #   end
        #
        # @example restriction using a Hash
        #  restriction = relation.restrict(id: 1)
        #
        # @example restriction using an Array
        #  restriction = relation.restrict([[:id, 1]])
        #
        # @param [Array] args
        #   optional arguments
        #
        # @yield [context]
        #   optional block to restrict the tuples with
        #
        # @yieldparam [Evaluator::Context] context
        #   the context to evaluate the restriction with
        #
        # @yieldreturn [Function, #call]
        #   predicate to restrict the tuples with
        #
        # @return [Restriction]
        #
        # @api public
        def restrict(*args, &block)
          Restriction.new(self, coerce_to_predicate(*args, &block))
        end

      private

        # Coerce the arguments and block into a predicate
        #
        # @param [Function, #call] predicate
        #   optional predicate
        #
        # @yield [relation]
        #   optional block to restrict the tuples with
        #
        # @yieldparam [Evaluator::Context] context
        #   the context to evaluate the restriction with
        #
        # @yieldreturn [Function, #call]
        #   predicate to restrict the tuples with
        #
        # @return [Function, #call]
        #
        # @api private
        def coerce_to_predicate(predicate = Undefined)
          case predicate
          when Undefined
            Evaluator::Context.new(header) { |context| yield context }.yield
          when Enumerable
            predicate.reduce(TAUTOLOGY) do |entry, (name, value)|
              entry.and(header[name].eq(value))
            end
          else
            predicate
          end
        end

      end # module Methods

      Relation.class_eval { include Methods }

    end # class Restriction
  end # module Algebra
end # module Axiom