dagnabit 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +5 -0
- data/.document +5 -0
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +186 -0
- data/Rakefile +69 -0
- data/VERSION.yml +5 -0
- data/bin/dagnabit-test +53 -0
- data/dagnabit.gemspec +124 -0
- data/init.rb +1 -0
- data/lib/dagnabit.rb +17 -0
- data/lib/dagnabit/activation.rb +60 -0
- data/lib/dagnabit/link/associations.rb +18 -0
- data/lib/dagnabit/link/class_methods.rb +43 -0
- data/lib/dagnabit/link/configuration.rb +40 -0
- data/lib/dagnabit/link/cycle_prevention.rb +31 -0
- data/lib/dagnabit/link/named_scopes.rb +65 -0
- data/lib/dagnabit/link/transitive_closure_link_model.rb +86 -0
- data/lib/dagnabit/link/transitive_closure_recalculation.rb +17 -0
- data/lib/dagnabit/link/transitive_closure_recalculation/on_create.rb +104 -0
- data/lib/dagnabit/link/transitive_closure_recalculation/on_destroy.rb +125 -0
- data/lib/dagnabit/link/transitive_closure_recalculation/on_update.rb +13 -0
- data/lib/dagnabit/link/transitive_closure_recalculation/utilities.rb +56 -0
- data/lib/dagnabit/link/validations.rb +26 -0
- data/lib/dagnabit/node/associations.rb +84 -0
- data/lib/dagnabit/node/class_methods.rb +74 -0
- data/lib/dagnabit/node/configuration.rb +26 -0
- data/lib/dagnabit/node/neighbors.rb +73 -0
- data/test/connections/native_postgresql/connection.rb +17 -0
- data/test/connections/native_sqlite3/connection.rb +24 -0
- data/test/dagnabit/link/test_associations.rb +61 -0
- data/test/dagnabit/link/test_class_methods.rb +102 -0
- data/test/dagnabit/link/test_configuration.rb +38 -0
- data/test/dagnabit/link/test_cycle_prevention.rb +64 -0
- data/test/dagnabit/link/test_named_scopes.rb +32 -0
- data/test/dagnabit/link/test_transitive_closure_link_model.rb +69 -0
- data/test/dagnabit/link/test_transitive_closure_recalculation.rb +139 -0
- data/test/dagnabit/link/test_validations.rb +39 -0
- data/test/dagnabit/node/test_associations.rb +147 -0
- data/test/dagnabit/node/test_class_methods.rb +49 -0
- data/test/dagnabit/node/test_configuration.rb +29 -0
- data/test/dagnabit/node/test_neighbors.rb +91 -0
- data/test/helper.rb +27 -0
- data/test/models/beta_node.rb +3 -0
- data/test/models/custom_data_link.rb +4 -0
- data/test/models/customized_link.rb +7 -0
- data/test/models/customized_link_node.rb +4 -0
- data/test/models/link.rb +4 -0
- data/test/models/node.rb +3 -0
- data/test/schema/schema.rb +51 -0
- metadata +165 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
print "Using native PostgreSQL\n"
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = {
|
8
|
+
'ActiveRecord::Base' => {
|
9
|
+
:adapter => 'postgresql',
|
10
|
+
:database => 'dagnabit_test',
|
11
|
+
:username => 'dagnabit_test',
|
12
|
+
:password => 'dagnabit_test',
|
13
|
+
:hostname => 'localhost'
|
14
|
+
}
|
15
|
+
};
|
16
|
+
|
17
|
+
ActiveRecord::Base.establish_connection('ActiveRecord::Base')
|
@@ -0,0 +1,24 @@
|
|
1
|
+
print "Using native SQLite3\n"
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# The SQLite3 Ruby driver can be present as either a gem or directly present in
|
6
|
+
# the Ruby library load path. This load method addresses these two situations.
|
7
|
+
begin
|
8
|
+
require 'sqlite3'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rubygems'
|
11
|
+
gem 'sqlite3-ruby'
|
12
|
+
require 'sqlite3'
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
16
|
+
|
17
|
+
class SqliteError < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
ActiveRecord::Base.configurations = {
|
21
|
+
'ActiveRecord::Base' => { :adapter => 'sqlite3', :database => ':memory:' }
|
22
|
+
};
|
23
|
+
|
24
|
+
ActiveRecord::Base.establish_connection('ActiveRecord::Base')
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestAssociations < ActiveRecord::TestCase
|
6
|
+
class Node < ActiveRecord::Base
|
7
|
+
set_table_name 'nodes'
|
8
|
+
end
|
9
|
+
|
10
|
+
class Link < ActiveRecord::Base
|
11
|
+
set_table_name 'edges'
|
12
|
+
extend Dagnabit::Link::Configuration
|
13
|
+
configure_acts_as_dag_link({})
|
14
|
+
extend Dagnabit::Link::Associations
|
15
|
+
end
|
16
|
+
|
17
|
+
class CustomLink < ActiveRecord::Base
|
18
|
+
set_table_name 'other_name_edges'
|
19
|
+
|
20
|
+
extend Dagnabit::Link::Configuration
|
21
|
+
configure_acts_as_dag_link(:ancestor_id_column => 'the_ancestor_id',
|
22
|
+
:descendant_id_column => 'the_descendant_id',
|
23
|
+
:transitive_closure_table_name => 'my_transitive_closure')
|
24
|
+
|
25
|
+
extend Dagnabit::Link::Associations
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup
|
29
|
+
@node = Node.new
|
30
|
+
@link = Link.new
|
31
|
+
@custom_link = CustomLink.new
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'add an ancestor association proxy' do
|
35
|
+
@link.ancestor = @node
|
36
|
+
@link.save
|
37
|
+
assert_equal @node, @link.reload.ancestor
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'add a descendant association proxy' do
|
41
|
+
@link.descendant = @node
|
42
|
+
@link.save
|
43
|
+
assert_equal @node, @link.reload.descendant
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'permit customization of the ancestor association foreign key' do
|
47
|
+
@custom_link.ancestor = @node
|
48
|
+
@custom_link.save
|
49
|
+
@custom_link = CustomLink.find(@custom_link.id)
|
50
|
+
assert_equal @node, @custom_link.ancestor
|
51
|
+
end
|
52
|
+
|
53
|
+
should 'permit customization of the descendant association foreign key' do
|
54
|
+
@custom_link.descendant = @node
|
55
|
+
@custom_link.save
|
56
|
+
@custom_link = CustomLink.find(@custom_link.id)
|
57
|
+
assert_equal @node, @custom_link.descendant
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestClassMethods < ActiveRecord::TestCase
|
6
|
+
should 'include a method to build edges' do
|
7
|
+
n1 = ::Node.new
|
8
|
+
n2 = ::Node.new
|
9
|
+
|
10
|
+
link = ::Link.build_edge(n1, n2)
|
11
|
+
assert_equal n1, link.ancestor
|
12
|
+
assert_equal n2, link.descendant
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'permit custom data to be included on edges' do
|
16
|
+
n1 = ::Node.new
|
17
|
+
n2 = ::Node.new
|
18
|
+
|
19
|
+
link = CustomDataLink.build_edge(n1, n2, :data => 'foobar')
|
20
|
+
assert_equal 'foobar', link.data
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'connect' do
|
24
|
+
should 'connect two nodes' do
|
25
|
+
n1 = ::Node.new
|
26
|
+
n2 = ::Node.new
|
27
|
+
|
28
|
+
::Link.connect(n1, n2)
|
29
|
+
link = ::Link.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n2.id })
|
30
|
+
|
31
|
+
assert_not_nil link
|
32
|
+
assert_equal n1, link.ancestor
|
33
|
+
assert_equal n2, link.descendant
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'permit custom data to be included on edges' do
|
37
|
+
n1 = ::Node.new
|
38
|
+
n2 = ::Node.new
|
39
|
+
|
40
|
+
CustomDataLink.connect(n1, n2, :data => 'foobar')
|
41
|
+
link = CustomDataLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n2.id })
|
42
|
+
|
43
|
+
assert_equal 'foobar', link.data
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'return false if connection fails due to node invalidity' do
|
47
|
+
n1 = ::Node.new
|
48
|
+
n2 = ::Node.new
|
49
|
+
|
50
|
+
n1.stubs(:valid?).returns(false)
|
51
|
+
|
52
|
+
result = ::Link.connect(n1, n2)
|
53
|
+
assert_equal false, result
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'not save endpoints if connection fails due to node invalidity' do
|
57
|
+
n1 = ::Node.new
|
58
|
+
n2 = ::Node.new
|
59
|
+
|
60
|
+
n1.stubs(:valid?).returns(false)
|
61
|
+
|
62
|
+
::Link.connect(n1, n2)
|
63
|
+
|
64
|
+
assert n1.new_record?
|
65
|
+
assert n2.new_record?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'path?' do
|
70
|
+
should 'return whether or not a path exists between two nodes' do
|
71
|
+
n1 = ::Node.new
|
72
|
+
n2 = ::Node.new
|
73
|
+
n3 = ::Node.new
|
74
|
+
n4 = ::Node.new
|
75
|
+
|
76
|
+
::Link.connect(n1, n2)
|
77
|
+
::Link.connect(n2, n3)
|
78
|
+
|
79
|
+
assert ::Link.path?(n1, n2)
|
80
|
+
assert ::Link.path?(n1, n3)
|
81
|
+
assert !::Link.path?(n1, n4)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'paths' do
|
86
|
+
should 'return all paths between two nodes' do
|
87
|
+
n1 = ::Node.new
|
88
|
+
n2 = ::Node.new
|
89
|
+
n3 = ::Node.new
|
90
|
+
|
91
|
+
::Link.connect(n1, n2)
|
92
|
+
::Link.connect(n2, n3)
|
93
|
+
|
94
|
+
paths = ::Link.paths(n1, n3)
|
95
|
+
assert_equal 1, paths.length
|
96
|
+
assert_equal n1, paths.first.ancestor
|
97
|
+
assert_equal n3, paths.first.descendant
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestConfiguration < ActiveRecord::TestCase
|
6
|
+
should 'have default and settable names for transitive closure model and column names' do
|
7
|
+
default_model = Class.new(ActiveRecord::Base) do
|
8
|
+
set_table_name 'edges'
|
9
|
+
|
10
|
+
acts_as_dag_link
|
11
|
+
end
|
12
|
+
|
13
|
+
custom_model = Class.new(ActiveRecord::Base) do
|
14
|
+
set_table_name 'other_name_edges'
|
15
|
+
|
16
|
+
acts_as_dag_link :ancestor_id_column => 'the_ancestor_id',
|
17
|
+
:descendant_id_column => 'the_descendant_id',
|
18
|
+
:transitive_closure_table_name => 'my_transitive_closure',
|
19
|
+
:transitive_closure_class_name => 'MyTransitiveClosureLink'
|
20
|
+
end
|
21
|
+
|
22
|
+
assert_equal 'ancestor_id', default_model.ancestor_id_column
|
23
|
+
assert_equal 'descendant_id', default_model.descendant_id_column
|
24
|
+
assert_equal default_model.connection.quote_table_name('edges_transitive_closure_tuples'), default_model.transitive_closure_table_name
|
25
|
+
assert_equal 'TransitiveClosureLink', default_model.transitive_closure_class_name
|
26
|
+
|
27
|
+
assert_equal 'the_ancestor_id', custom_model.ancestor_id_column
|
28
|
+
assert_equal 'the_descendant_id', custom_model.descendant_id_column
|
29
|
+
assert_equal custom_model.connection.quote_table_name('my_transitive_closure'), custom_model.transitive_closure_table_name
|
30
|
+
assert_equal 'MyTransitiveClosureLink', custom_model.transitive_closure_class_name
|
31
|
+
end
|
32
|
+
|
33
|
+
should 'not have its configuration accessors on ActiveRecord::Base' do
|
34
|
+
assert !ActiveRecord::Base.respond_to?(:ancestor_id_column)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestCyclePrevention < ActiveRecord::TestCase
|
6
|
+
should 'prevent simple cycles from being saved' do
|
7
|
+
n1 = ::Node.create
|
8
|
+
n2 = ::Node.create
|
9
|
+
|
10
|
+
l1 = ::Link.create(:ancestor => n1, :descendant => n2)
|
11
|
+
l2 = ::Link.create(:ancestor => n2, :descendant => n1)
|
12
|
+
|
13
|
+
assert l2.new_record?
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'prevent self-referential nodes' do
|
17
|
+
n1 = ::Node.create
|
18
|
+
|
19
|
+
l1 = ::Link.create(:ancestor => n1, :descendant => n1)
|
20
|
+
assert l1.new_record?
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'prevent cycles from being saved' do
|
24
|
+
n1 = ::Node.create
|
25
|
+
n2 = ::Node.create
|
26
|
+
n3 = ::Node.create
|
27
|
+
n4 = ::Node.create
|
28
|
+
|
29
|
+
::Link.create(:ancestor => n1, :descendant => n2)
|
30
|
+
::Link.create(:ancestor => n2, :descendant => n3)
|
31
|
+
::Link.create(:ancestor => n3, :descendant => n4)
|
32
|
+
l = ::Link.create(:ancestor => n4, :descendant => n2)
|
33
|
+
|
34
|
+
assert l.new_record?
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'not prevent links from being saved multiple times in a row' do
|
38
|
+
n1 = ::Node.create
|
39
|
+
n2 = ::Node.create
|
40
|
+
|
41
|
+
l = ::Link.new(:ancestor => n1, :descendant => n2)
|
42
|
+
|
43
|
+
assert l.save
|
44
|
+
assert l.save, 'should have been able to save twice'
|
45
|
+
end
|
46
|
+
|
47
|
+
should 'prevent cycles from being saved in customized links' do
|
48
|
+
n1 = ::Node.create
|
49
|
+
n2 = ::Node.create
|
50
|
+
n3 = ::Node.create
|
51
|
+
|
52
|
+
::CustomizedLink.create(:ancestor => n1, :descendant => n2)
|
53
|
+
::CustomizedLink.create(:ancestor => n1, :descendant => n3)
|
54
|
+
l3 = ::CustomizedLink.create(:ancestor => n3, :descendant => n1)
|
55
|
+
|
56
|
+
assert l3.new_record?
|
57
|
+
end
|
58
|
+
|
59
|
+
should 'not execute on links that have no ancestor or descendant' do
|
60
|
+
assert_nothing_raised { ::Link.create }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestNamedScopes < ActiveRecord::TestCase
|
6
|
+
class OtherNode < ActiveRecord::Base
|
7
|
+
set_table_name 'nodes'
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@n1 = ::Node.new
|
12
|
+
@n2 = OtherNode.new
|
13
|
+
@n3 = ::Node.new
|
14
|
+
|
15
|
+
@l1 = ::Link.build_edge(@n1, @n2)
|
16
|
+
@l2 = ::Link.build_edge(@n2, @n3)
|
17
|
+
@l1.save
|
18
|
+
@l2.save
|
19
|
+
end
|
20
|
+
|
21
|
+
should 'scope links by ancestor type' do
|
22
|
+
links = ::Link.ancestor_type(OtherNode.name)
|
23
|
+
assert_equal [@l2], links
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'scope links by descendant type' do
|
27
|
+
links = ::Link.descendant_type(OtherNode.name)
|
28
|
+
assert_equal [@l1], links
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestTransitiveClosureLinkModel < ActiveRecord::TestCase
|
6
|
+
def setup
|
7
|
+
@model = ::Link::TransitiveClosureLink.new
|
8
|
+
@model.class.set_table_name 'edges_transitive_closure_tuples'
|
9
|
+
@node = ::Node.new
|
10
|
+
end
|
11
|
+
|
12
|
+
should 'belong to an ancestor' do
|
13
|
+
@model.ancestor = @node
|
14
|
+
@model.save
|
15
|
+
|
16
|
+
@model = ::Link::TransitiveClosureLink.find(@model.id)
|
17
|
+
assert_equal @node, @model.ancestor
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'belong to a descendant' do
|
21
|
+
@model.descendant = @node
|
22
|
+
@model.save
|
23
|
+
|
24
|
+
@model = ::Link::TransitiveClosureLink.find(@model.id)
|
25
|
+
assert_equal @node, @model.descendant
|
26
|
+
end
|
27
|
+
|
28
|
+
# TODO: wrap tests in transaction
|
29
|
+
should 'support find(:all) queries including associations' do
|
30
|
+
::Link::TransitiveClosureLink.delete_all
|
31
|
+
|
32
|
+
n1 = ::Node.new
|
33
|
+
n2 = ::Node.new
|
34
|
+
::Link::TransitiveClosureLink.create(:ancestor => n1, :descendant => n2)
|
35
|
+
|
36
|
+
links = ::Link::TransitiveClosureLink.find(:all, :include => :ancestor)
|
37
|
+
assert_equal 1, links.length
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'support scoping by ancestor type' do
|
41
|
+
::Link::TransitiveClosureLink.delete_all
|
42
|
+
|
43
|
+
n1 = ::BetaNode.new
|
44
|
+
n2 = ::Node.new
|
45
|
+
|
46
|
+
::Link::TransitiveClosureLink.create(:ancestor => n1, :descendant => n2)
|
47
|
+
|
48
|
+
links = ::Link::TransitiveClosureLink.ancestor_type(::BetaNode.name)
|
49
|
+
|
50
|
+
assert_equal 1, links.length
|
51
|
+
assert_equal n1, links.first.ancestor
|
52
|
+
end
|
53
|
+
|
54
|
+
should 'support scoping by descendant type' do
|
55
|
+
::Link::TransitiveClosureLink.delete_all
|
56
|
+
|
57
|
+
n1 = ::Node.new
|
58
|
+
n2 = ::BetaNode.new
|
59
|
+
|
60
|
+
::Link::TransitiveClosureLink.create(:ancestor => n1, :descendant => n2)
|
61
|
+
|
62
|
+
links = ::Link::TransitiveClosureLink.descendant_type(::BetaNode.name)
|
63
|
+
|
64
|
+
assert_equal 1, links.length
|
65
|
+
assert_equal n2, links.first.descendant
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestTransitiveClosureRecalculation < ActiveRecord::TestCase
|
6
|
+
TransitiveClosureLink = ::Link::TransitiveClosureLink
|
7
|
+
CustomTransitiveClosureLink = CustomizedLink::TransitiveClosureLink
|
8
|
+
TransitiveClosureCustomDataLink = CustomDataLink::TransitiveClosureLink
|
9
|
+
|
10
|
+
should 'recalculate transitive closure on create' do
|
11
|
+
n1 = ::Node.create
|
12
|
+
n2 = ::Node.create
|
13
|
+
n3 = ::Node.create
|
14
|
+
|
15
|
+
::Link.create(:ancestor => n1, :descendant => n2)
|
16
|
+
::Link.create(:ancestor => n2, :descendant => n3)
|
17
|
+
|
18
|
+
tc = TransitiveClosureLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n3.id })
|
19
|
+
assert_not_nil tc, 'expected to find path from n1 to n3'
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'transfer custom data attributes to transitive closure' do
|
23
|
+
n1 = ::Node.create
|
24
|
+
n2 = ::Node.create
|
25
|
+
n3 = ::Node.create
|
26
|
+
|
27
|
+
CustomDataLink.create(:ancestor => n1, :descendant => n2, :data => 'foo')
|
28
|
+
CustomDataLink.create(:ancestor => n2, :descendant => n3, :data => 'bar')
|
29
|
+
|
30
|
+
tc1 = TransitiveClosureCustomDataLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n2.id })
|
31
|
+
tc2 = TransitiveClosureCustomDataLink.find(:first, :conditions => { :ancestor_id => n2.id, :descendant_id => n3.id })
|
32
|
+
tc3 = TransitiveClosureCustomDataLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n3.id })
|
33
|
+
|
34
|
+
assert_equal 'foo', tc1.data, 'expected to find custom data attribute on n1->n2 edge'
|
35
|
+
assert_equal 'bar', tc2.data, 'expected to find custom data attribute on n2->n3 edge'
|
36
|
+
assert_not_nil tc3, 'expected to find path from n1 to n3'
|
37
|
+
assert_nil tc3.data, 'expected to find no custom data attribute on n1->n3 path'
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'recalculate transitive closure on destroy' do
|
41
|
+
n1 = ::Node.create
|
42
|
+
n2 = ::Node.create
|
43
|
+
n3 = ::Node.create
|
44
|
+
|
45
|
+
l1 = ::Link.create(:ancestor => n1, :descendant => n2)
|
46
|
+
l2 = ::Link.create(:ancestor => n2, :descendant => n3)
|
47
|
+
|
48
|
+
l2.destroy
|
49
|
+
|
50
|
+
tc = TransitiveClosureLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n2.id })
|
51
|
+
assert_not_nil tc, 'expected to find path from n1 to n2'
|
52
|
+
|
53
|
+
tc = TransitiveClosureLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n3.id })
|
54
|
+
assert_nil tc, 'expected to not find path from n1 to n3'
|
55
|
+
end
|
56
|
+
|
57
|
+
should 'include edges in the graph when reconstructing the transitive closure on destroy' do
|
58
|
+
n1 = ::Node.create
|
59
|
+
n2 = ::Node.create
|
60
|
+
n3 = ::Node.create
|
61
|
+
|
62
|
+
l1 = ::Link.create(:ancestor => n1, :descendant => n2)
|
63
|
+
l2 = ::Link.create(:ancestor => n2, :descendant => n3)
|
64
|
+
l3 = ::Link.create(:ancestor => n1, :descendant => n3)
|
65
|
+
|
66
|
+
l1.destroy
|
67
|
+
l2.destroy
|
68
|
+
|
69
|
+
tc = TransitiveClosureLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n3.id })
|
70
|
+
assert_not_nil tc, 'expected to find path from n1 to n3'
|
71
|
+
end
|
72
|
+
|
73
|
+
should 'recalculate transitive closure on update' do
|
74
|
+
n1 = ::Node.create
|
75
|
+
n2 = ::Node.create
|
76
|
+
n3 = ::Node.create
|
77
|
+
n4 = ::Node.create
|
78
|
+
|
79
|
+
l1 = ::Link.create(:ancestor => n1, :descendant => n2)
|
80
|
+
l2 = ::Link.create(:ancestor => n2, :descendant => n3)
|
81
|
+
|
82
|
+
l2.update_attribute(:descendant, n4)
|
83
|
+
|
84
|
+
tc = TransitiveClosureLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n4.id })
|
85
|
+
assert_not_nil tc, 'expected to find path from n1 to n4'
|
86
|
+
|
87
|
+
tc = TransitiveClosureLink.find(:first, :conditions => { :ancestor_id => n1.id, :descendant_id => n3.id })
|
88
|
+
assert_nil tc, 'expected to not find path from n1 to n3'
|
89
|
+
end
|
90
|
+
|
91
|
+
should 'recalculate transitive closure on create using custom-configured link' do
|
92
|
+
n1 = ::Node.create
|
93
|
+
n2 = ::Node.create
|
94
|
+
n3 = ::Node.create
|
95
|
+
|
96
|
+
CustomizedLink.create(:ancestor => n1, :descendant => n2)
|
97
|
+
CustomizedLink.create(:ancestor => n2, :descendant => n3)
|
98
|
+
|
99
|
+
tc = CustomTransitiveClosureLink.find(:first, :conditions => { :the_ancestor_id => n1.id, :the_descendant_id => n3.id })
|
100
|
+
assert_not_nil tc, 'expected to find path from n1 to n3'
|
101
|
+
end
|
102
|
+
|
103
|
+
should 'recalculate transitive closure on destroy using custom-configured link' do
|
104
|
+
n1 = ::Node.create
|
105
|
+
n2 = ::Node.create
|
106
|
+
n3 = ::Node.create
|
107
|
+
|
108
|
+
l1 = CustomizedLink.create(:ancestor => n1, :descendant => n2)
|
109
|
+
l2 = CustomizedLink.create(:ancestor => n2, :descendant => n3)
|
110
|
+
|
111
|
+
l2.destroy
|
112
|
+
|
113
|
+
tc = CustomTransitiveClosureLink.find(:first, :conditions => { :the_ancestor_id => n1.id, :the_descendant_id => n2.id })
|
114
|
+
assert_not_nil tc, 'expected to find path from n1 to n2'
|
115
|
+
|
116
|
+
tc = CustomTransitiveClosureLink.find(:first, :conditions => { :the_ancestor_id => n1.id, :the_descendant_id => n3.id })
|
117
|
+
assert_nil tc, 'expected to not find path from n1 to n3'
|
118
|
+
end
|
119
|
+
|
120
|
+
should 'recalculate transitive closure on update using custom-configured link' do
|
121
|
+
n1 = ::Node.create
|
122
|
+
n2 = ::Node.create
|
123
|
+
n3 = ::Node.create
|
124
|
+
n4 = ::Node.create
|
125
|
+
|
126
|
+
l1 = CustomizedLink.create(:ancestor => n1, :descendant => n2)
|
127
|
+
l2 = CustomizedLink.create(:ancestor => n2, :descendant => n3)
|
128
|
+
|
129
|
+
l2.update_attribute(:descendant, n4)
|
130
|
+
|
131
|
+
tc = CustomTransitiveClosureLink.find(:first, :conditions => { :the_ancestor_id => n1.id, :the_descendant_id => n4.id })
|
132
|
+
assert_not_nil tc, 'expected to find path from n1 to n4'
|
133
|
+
|
134
|
+
tc = CustomTransitiveClosureLink.find(:first, :conditions => { :the_ancestor_id => n1.id, :the_descendant_id => n3.id })
|
135
|
+
assert_nil tc, 'expected to not find path from n1 to n3'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|