# encoding: utf-8 module Axiom class Relation module Operation # A class representing a limited relation class Limit < Relation include Unary include Equalizer.new(:operand, :limit) # Return the limit # # @example # limit = limited_relation.limit # # @return [Integer] # # @api public attr_reader :limit # The relation sort order # # @return [Operation::Sorted::DirectionSet] # # @api private attr_reader :directions # Instantiate a new Limit # # @example # limited_relation = Limit.new(operand, limit) # # @param [Relation] operand # the relation to limit # @param [Integer] limit # the maximum number of tuples in the limited relation # # @return [Limit] # # @api public def self.new(operand, limit) assert_sorted_operand(operand) assert_valid_limit(limit) super end # Assert the operand is sorted # # @param [Relation] operand # # @return [undefined] # # @raise [SortededRelationRequiredError] # raised if the operand is unsorted # # @api private def self.assert_sorted_operand(operand) if operand.header.size != operand.directions.size fail SortededRelationRequiredError, 'can only limit a sorted operand' end end # Assert the limit is valid # # @param [Integer] limit # # @return [undefined] # # @raise [InvalidLimitError] # raised if the limit is less than 0 # # @api private def self.assert_valid_limit(limit) if limit.nil? || limit < 0 fail InvalidLimitError, "limit must be greater than or equal to 0, but was #{limit.inspect}" end end private_class_method :assert_sorted_operand, :assert_valid_limit # Initialize a Limit # # @param [Relation] operand # the relation to limit # @param [Integer] limit # the maximum number of tuples in the limited relation # # @return [undefined] # # @api private def initialize(operand, limit) super(operand) @limit = limit @directions = operand.directions end # Iterate over each tuple in the set # # @example # limited_relation = Limit.new(operand, limit) # limited_relation.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_with_index do |tuple, index| break if @limit == index yield tuple end self end # Raise an exception when inserting into the Limit # # @example # limit.insert(other) # => ImmutableRelationError raised # # @return [undefined] # # @raise [ImmutableRelationError] # raised when inserting into the limit # # @api public def insert(*) fail ImmutableRelationError, 'inserting into a limit is impossible' end # Raise an exception when deleting from the Limit # # @example # limit.delete(other) # => ImmutableRelationError raised # # @return [undefined] # # @raise [ImmutableRelationError] # raised when deleting from the limit # # @api public def delete(*) fail ImmutableRelationError, 'deleting from a limit is impossible' end module Methods # Default block used in #one DEFAULT_ONE_BLOCK = -> {} # Maximum number of tuples to take in #one ONE_LIMIT = 2 # Return a relation with n tuples # # @example # limited_relation = relation.take(5) # # @param [Integer] limit # the maximum number of tuples in the limited relation # # @return [Limit] # # @api public def take(limit) Limit.new(self, limit) end # Return a relation with the first n tuples # # @example with no limit # limited_relation = relation.first # # @example with a limit # limited_relation = relation.first(5) # # @param [Integer] limit # optional number of tuples from the beginning of the relation # # @return [Limit] # # @api public def first(limit = 1) take(limit) end # Return a relation with the last n tuples # # @example with no limit # limited_relation = relation.last # # @example with a limit # limited_relation = relation.last(5) # # @param [Integer] limit # optional number of tuples from the end of the relation # # @return [Limit] # # @api public def last(limit = 1) reverse.take(limit).reverse end # Return a tuple if the relation contains exactly one tuple # # @example without a block # tuple = relation.one # # @example with a block # tuple = relation.one { ... } # # @yieldreturn [Object] # # @return [Tuple] # # @raise [NoTuplesError] # raised if no tuples are returned # @raise [ManyTuplesError] # raised if more than one tuple is returned # # @api public def one(&block) block ||= DEFAULT_ONE_BLOCK tuples = take(ONE_LIMIT).to_a assert_no_more_than_one_tuple(tuples.size) tuples.first or block.yield or fail NoTuplesError, 'one tuple expected, but was an empty set' end private # Assert no more than one tuple is returned # # @return [undefined] # # @raise [ManyTuplesError] # raised if more than one tuple is returned # # @api private def assert_no_more_than_one_tuple(size) if size > 1 fail( ManyTuplesError, "one tuple expected, but set contained #{count} tuples" ) end end end # module Methods Relation.class_eval { include Methods } end # class Limit end # module Operation end # class Relation end # module Axiom