lib/axiom/relation/operation/limit.rb
# 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)
return if size <= 1
fail(
ManyTuplesError,
"one tuple expected, but set contained #{count} tuples"
)
end
end # module Methods
Relation.class_eval { include Methods }
end # class Limit
end # module Operation
end # class Relation
end # module Axiom