From 5d78f870f887e41bbf460b8e06fd613178c767fb Mon Sep 17 00:00:00 2001 From: andreas Date: Tue, 25 Aug 2009 22:48:32 +0200 Subject: [PATCH] node aggregation - fixed aggregation over aggregation bug. Added more RSpecs. [#65] --- lib/neo4j/mixins/aggregate_node_mixin.rb | 24 ++- test/neo4j/aggregate_node_mixin_spec.rb | 192 +++++++++++++++++------ 2 files changed, 151 insertions(+), 65 deletions(-) diff --git a/lib/neo4j/mixins/aggregate_node_mixin.rb b/lib/neo4j/mixins/aggregate_node_mixin.rb index 5f1e4f407..d7d59e8d2 100644 --- a/lib/neo4j/mixins/aggregate_node_mixin.rb +++ b/lib/neo4j/mixins/aggregate_node_mixin.rb @@ -1,3 +1,5 @@ +require 'set' + module Neo4j module NodeMixin @@ -336,20 +338,21 @@ def map_value(&map_func) # Create a group key for given node def group_key_of(node) - if @map_func.nil? - @group_by.map{|key| node[key]} - else - args = @group_by.map{|key| node[key]} - raise "Wrong number of argument of map_value function, expected #{args.size} args but it takes #{@map_func.arity} args" if @map_func.arity != args.size - result = @map_func.call(*args) - result = [result] unless result.kind_of? Enumerable + values = @group_by.map{|key| node[key]} + if !@map_func.nil? + raise "Wrong number of argument of map_value function, expected #{values.size} args but it takes #{@map_func.arity} args" if @map_func.arity != values.size + values = @map_func.call(*values) + values = [values] unless values.kind_of? Enumerable end + + # check all values and expand enumerable values + values.inject(Set.new) {|result, value| value.respond_to?(:to_a) ? result.merge(value.to_a) : result << value }.to_a end # Executes the DSL and creates the specified groups. def execute(nodes = @nodes) nodes.each do |node| - execute(node) if node.kind_of?(Enumerable) +# execute(node) if node.kind_of?(Enumerable) group_key = group_key_of(node) @@ -360,8 +363,6 @@ def execute(nodes = @nodes) group_key = [group_key.join('_')] unless @by_each group_key.each do |key| - puts "KEY #{key}" if @by_each - group_node = @base_node.relationships.outgoing(key).nodes.first if group_node.nil? group_node = AggregateGroupNode.create(key) @@ -399,16 +400,13 @@ def get_property(key) super(key) value = super(key) return value unless value.nil? -# puts "GET PROPERTY #{key} value nil on #{self.object_id}" # traverse all sub nodes and get their properties AggregatedProperties.new(relationships.outgoing.nodes, key) end def set_property(key, value) super key, value - val = self.get_property(key) -# puts "SET PROPERTY #{key} value #{val} on #{self.object_id}" end end diff --git a/test/neo4j/aggregate_node_mixin_spec.rb b/test/neo4j/aggregate_node_mixin_spec.rb index 0aed5f144..348431b48 100644 --- a/test/neo4j/aggregate_node_mixin_spec.rb +++ b/test/neo4j/aggregate_node_mixin_spec.rb @@ -286,7 +286,7 @@ class MyAggregateNode end it "should not append a node to an aggregate if it already exist in the aggregate" do - pending "Not sure if we want that. Will get bad performance if we have to check it each time we add a node to a aggregate" + pending "Not sure if we want that. Will get bad performance if we have to check it each time we add a node to a aggregate" agg_node = MyAggregateNode.new agg_node.aggregate.group_by(:colour) @@ -303,92 +303,180 @@ class MyAggregateNode end end -describe "Aggregates, over another aggregate" do - before(:all) do + +# +#describe "Aggregates << another_aggregate" do +# before(:all) do +# start +# +# Neo4j::Transaction.new +# @set1 = [Neo4j::Node.new, Neo4j::Node.new] +# @set2 = [Neo4j::Node.new, Neo4j::Node.new] +# @set1[0][:colour] = 'red' +# @set1[1][:colour] = 'blue' +# +# @set2[0][:colour] = 'red'; @set2[0][:age] = 1 +# @set2[1][:colour] = 'blue'; @set2[1][:age] = 2 +# end +# +# after(:all) do +# stop +# end +# +# it "should add node into existing groups using the << operator" do +# pending "not sure how the << operator should work" +# agg1 = MyAggregateNode.new +# agg2 = MyAggregateNode.new +# +# agg1.aggregate(@set1).group_by(:colour).execute +# agg2.aggregate(@set2).group_by(:age).execute +# +# # when +# agg1 << agg2 +# +# # then +# agg1[:red].aggregate_size.should == 2 +# agg1[:red].should include(@set1[0], @set2[0]) +# +# agg1[:blue].aggregate_size.should == 2 +# agg1[:blue].should include(@set1[1], @set2[1]) +# +# @set2[0].relationships.incoming.nodes.each {|x| puts "OUTGOING #{x.props.inspect}"} +# end +# +#end +# +# +# +# +describe "Aggregates, each node should know which aggregate(s) it belongs to" do + before(:each) do start Neo4j::Transaction.new - @set1 = [Neo4j::Node.new, Neo4j::Node.new] - @set2 = [Neo4j::Node.new, Neo4j::Node.new] - @set1[0][:colour] = 'red' - @set1[1][:colour] = 'blue' + @set = [] + 4.times {@set << Neo4j::Node.new} + @set[0][:colour] = 'red'; @set[0][:name] = "a" + @set[1][:colour] = 'red'; @set[1][:name] = "b" + @set[2][:colour] = 'red'; @set[2][:name] = "c" + @set[3][:colour] = 'blue'; @set[3][:name] = "d" - @set2[0][:colour] = 'red'; @set2[0][:age] = 1 - @set2[1][:colour] = 'blue'; @set2[1][:age] = 2 + + + # aggreate first on name + @agg1 = MyAggregateNode.new + @agg1.aggregate(@set).group_by(:name).execute + + #use this name aggregate and aggregate on colour + # + # agg1 set agg2 + # a -- @set[0] --+ + # b -- @set[1] --+-- red + # c -- @set[2] --+ + # d -- @set[3] ---- blue + # + @agg2 = MyAggregateNode.new + @agg2.aggregate(@set).group_by(:colour).execute end - after(:all) do + after(:each) do stop end - it "should add node into existing groups using the << operator" do - agg1 = MyAggregateNode.new - agg2 = MyAggregateNode.new - - agg1.aggregate(@set1).group_by(:colour).execute - agg2.aggregate(@set2).group_by(:age).execute - - # when - agg1 << agg2 + it "should know which aggregate it belongs to" do + @set[0].aggregates.to_a.size.should == 2 + @set[1].aggregates.to_a.size.should == 2 + @set[0].aggregates.should include(@agg1, @agg2) + end - # then - agg1[:red].aggregate_size.should == 2 - agg1[:red].should include(@set1[0], @set2[0]) + it "should know which aggregate group it belongs to" do + # set[0] should belong to group agg1[a] and agg2[red] + @set[0].aggregate_groups.to_a.size.should == 2 + @set[0].aggregate_groups.should include(@agg1['a'], @agg2['red']) - agg1[:blue].aggregate_size.should == 2 - agg1[:blue].should include(@set1[1], @set2[1]) + # set[2] should belong to group agg1[c] and agg2[red] + @set[2].aggregate_groups.to_a.size.should == 2 + @set[2].aggregate_groups.should include(@agg1['c'], @agg2['red']) - @set2[0].relationships.incoming.nodes.each {|x| puts "OUTGOING #{x.props.inspect}"} + # set[3] should belong to group agg[d] and agg2[blue] + @set[3].aggregate_groups.to_a.size.should == 2 + @set[3].aggregate_groups.should include(@agg1['d'], @agg2['blue']) end end -describe "Aggregates, each node should know which aggregate(s) it belongs to" do +describe "Aggregates, over another aggregate" do before(:each) do start Neo4j::Transaction.new - @set1 = [Neo4j::Node.new, Neo4j::Node.new] - @set2 = [Neo4j::Node.new, Neo4j::Node.new] - @set1[0][:colour] = 'red' - @set1[1][:colour] = 'blue' - - @set2[0][:colour] = 'red'; @set2[0][:age] = 1 # belongs to two aggregates - @set2[1][:colour] = 'blue'; @set2[1][:age] = 2 + @set = [] + 4.times {@set << Neo4j::Node.new} + @set[0][:colour] = 'red'; @set[0][:name] = "a" + @set[1][:colour] = 'red'; @set[1][:name] = "b" + @set[2][:colour] = 'red'; @set[2][:name] = "c" + @set[3][:colour] = 'blue'; @set[3][:name] = "d" end - after(:each) do + after(:all) do stop end - it "should know which aggregate it belongs to" do + it "should allow to aggregate aggregate groups" do + # given + # agg2 agg1 set + # +-- a -- @set[0] + # red --|-- b -- @set[1] + # +-- c -- @set[2] + # blue ---- d -- @set[3] + # agg1 = MyAggregateNode.new + agg1.aggregate(@set).group_by(:name).execute + + # when agg2 = MyAggregateNode.new - agg1.aggregate(@set1).group_by(:colour).execute - agg2.aggregate(@set2).group_by(:age).execute + agg2.aggregate(agg1).group_by(:colour).execute - @set2[0].aggregates.to_a.size.should == 1 + # then + agg2.aggregate_size.should == 2 + agg2['red'].aggregate_size.should == 3 + agg2['red'].should include(agg1['a'], agg1['b'], agg1['c']) - agg1 << agg2 - - @set2[0].aggregates.to_a.size.should == 2 - @set2[0].aggregates.to_a.should include(agg1, agg2) + agg2['blue'].aggregate_size.should == 1 + agg2['blue'].should include(agg1['d']) end - it "should know which aggregate group it belongs to" do - agg1 = MyAggregateNode.new - agg2 = MyAggregateNode.new - agg1.aggregate(@set1).group_by(:colour).execute - agg2.aggregate(@set2).group_by(:age).execute + it "should know which aggregate it belongs to" do + agg1 = MyAggregateNode.new('agg1') + agg1.aggregate(@set).group_by(:name).execute + + # when + agg2 = MyAggregateNode.new('agg2') + agg2.aggregate(agg1).group_by(:colour).execute - @set2[0].aggregate_groups.each {|x| puts x.props.inspect} - @set2[0].aggregate_groups.to_a.size.should == 1 + # then + agg1['a'].aggregates.to_a.size.should == 1 + @set[0].aggregates.to_a.size.should == 1 - agg1 << agg2 + @set[0].aggregates.to_a.should include(agg1) + agg1['a'].aggregates.to_a.should include(agg2) + end + + it "should know which aggregate groups it belongs to" do + agg1 = MyAggregateNode.new('agg1') + agg1.aggregate(@set).group_by(:name).execute + + # when + agg2 = MyAggregateNode.new('agg2') + agg2.aggregate(agg1).group_by(:colour).execute + + # then + agg1['a'].aggregate_groups.to_a.size.should == 1 + @set[0].aggregate_groups.to_a.size.should == 1 - @set2[0].aggregate_groups.to_a.size.should == 2 - @set2[0].aggregate_groups.to_a.should include(agg1[:red], agg2[1]) + @set[0].aggregate_groups.to_a.should include(agg1['a']) + agg1['a'].aggregate_groups.to_a.should include(agg2['red']) end end