dagnabit 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|