lib/axiom/relation.rb in axiom-0.1.0 vs lib/axiom/relation.rb in axiom-0.1.1

- old
+ new

@@ -2,11 +2,11 @@ module Axiom # Abstract base class for Relation operations class Relation - include Enumerable, Visitable + include Enumerable, Visitable, Adamantium::Flat include Equalizer.new(:header, :to_set) # The relation header # # @return [Header] @@ -32,35 +32,49 @@ end # Instantiate a new Relation # # @example of a materialized Array based relation - # array = [ [ 1 ], [ 2 ], [ 3 ] ] - # relation = Relation.new([ [ :id, Integer ] ], array) + # array = [[1], [2], [3]] + # relation = Relation.new([[:id, Integer]], array) # # @example of a materialized Set based relation - # set = Set[ [ 1 ], [ 2 ], [ 3 ] ] - # relation = Relation.new([ [ :id, Integer ] ], set) + # set = Set[[1], [2], [3]] + # relation = Relation.new([[:id, Integer]], set) # # @example of a non-materialized Enumerator based relation - # enumerator = [ [ 1 ], [ 2 ], [ 3 ] ].each - # relation = Relation.new([ [ :id, Integer ] ], enumerator) + # enumerator = [[1], [2], [3]].each + # relation = Relation.new([[:id, Integer]], enumerator) # # @param [Array(Header, Enumerable)] args # # @return [Relation] # # @api public def self.new(*args) - last = args.last - if superclass.equal?(Object) && last.respond_to?(:size) && last.size.kind_of?(Integer) + if equal?(Relation) && materialized?(args[1]) Materialized.new(*args) else super end end + # Test if the tuples are materialized + # + # When tuples are nil, it means there are no tuples so it is the equivalent + # of specifying [] for the tuples. + # + # @param [Enumerable, nil] tuples + # + # @return [Boolean] + # + # @api private + def self.materialized?(tuples) + tuples.nil? || tuples.respond_to?(:size) && tuples.size.kind_of?(Integer) + end + private_class_method :materialized? + # Initialize a Relation # # @param [Header, #to_ary] header # the relation header # @param [Enumerable] tuples @@ -69,11 +83,11 @@ # @return [undefined] # # @api private def initialize(header, tuples) @header = Header.coerce(header) - @tuples = freeze_object(tuples) + @tuples = tuples end # Lookup an Attribute in the header given an attribute name # # @example @@ -103,37 +117,18 @@ # @return [self] # # @api public def each return to_enum unless block_given? - seen = Hash.new + seen = {} tuples.each do |tuple| tuple = Tuple.coerce(header, tuple) yield seen[tuple] = tuple unless seen.key?(tuple) end self end - # Return a tuple if the relation contains exactly one tuple - # - # @example - # tuple = relation.one - # - # @return [Tuple] - # - # @raise [NoTuplesError] - # raised if no tuples are returned - # @raise [ManyTuplesError] - # raised if more than one tuple is returned - # - # @api public - def one - tuples = to_a - assert_exactly_one_tuple(tuples.size) - tuples.first - end - # Return a relation that represents a replacement of a relation # # Delete the tuples from the relation that are not in the other relation, # then insert only new tuples. # @@ -199,11 +194,12 @@ # @return [Boolean] # # @api public def ==(other) other = coerce(other) - header == other.header && + other.kind_of?(Relation) && + header == other.header && to_set == other.to_set end # Test if there are no tuples # @@ -240,28 +236,13 @@ # # @return [Relation] # # @api private def self.coerce(header, object) - object.kind_of?(Relation) ? object : Relation.new(header, object) - end - - # Assert exactly one tuple is returned - # - # @return [undefined] - # - # @raise [NoTuplesError] - # raised if no tuples are returned - # @raise [ManyTuplesError] - # raised if more than one tuple is returned - # - # @api private - def assert_exactly_one_tuple(size) - if size.zero? - raise NoTuplesError, 'one tuple expected, but was an empty set' - elsif size > 1 - raise ManyTuplesError, - "one tuple expected, but set contained #{size} tuples" + if object.kind_of?(Relation) || ! object.kind_of?(Enumerable) + object + else + Relation.new(header, object) end end end # class Relation end # module Axiom