Skip to content

Commit

Permalink
node aggregation - fixed aggregation over aggregation bug. Added more…
Browse files Browse the repository at this point in the history
… RSpecs. [#65]
  • Loading branch information
andreas committed Aug 25, 2009
1 parent 5b0df82 commit 5d78f87
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 65 deletions.
24 changes: 11 additions & 13 deletions lib/neo4j/mixins/aggregate_node_mixin.rb
@@ -1,3 +1,5 @@
require 'set'

module Neo4j

module NodeMixin
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand Down
192 changes: 140 additions & 52 deletions test/neo4j/aggregate_node_mixin_spec.rb
Expand Up @@ -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)

Expand All @@ -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

0 comments on commit 5d78f87

Please sign in to comment.