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,39 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Link
|
5
|
+
class TestValidations < ActiveRecord::TestCase
|
6
|
+
def setup
|
7
|
+
@n1 = ::Node.new
|
8
|
+
@n2 = ::Node.new
|
9
|
+
@link = ::Link.new
|
10
|
+
end
|
11
|
+
|
12
|
+
should 'not permit a null ancestor' do
|
13
|
+
@link.ancestor = nil
|
14
|
+
assert !@link.save
|
15
|
+
end
|
16
|
+
|
17
|
+
should 'not permit an invalid ancestor' do
|
18
|
+
@n1.stubs(:valid?).returns(false)
|
19
|
+
|
20
|
+
@link.ancestor = @n1
|
21
|
+
@link.descendant = @n2
|
22
|
+
assert !@link.save
|
23
|
+
end
|
24
|
+
|
25
|
+
should 'not permit a null descendant' do
|
26
|
+
@link.descendant = nil
|
27
|
+
assert !@link.save
|
28
|
+
end
|
29
|
+
|
30
|
+
should 'not permit an invalid descendant' do
|
31
|
+
@n2.stubs(:valid?).returns(false)
|
32
|
+
|
33
|
+
@link.ancestor = @n1
|
34
|
+
@link.descendant = @n2
|
35
|
+
assert !@link.save
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Node
|
5
|
+
class TestAssociations < ActiveRecord::TestCase
|
6
|
+
class OtherLink < ActiveRecord::Base
|
7
|
+
set_table_name 'edges'
|
8
|
+
acts_as_dag_link
|
9
|
+
end
|
10
|
+
|
11
|
+
class OtherNode < ActiveRecord::Base
|
12
|
+
set_table_name 'nodes'
|
13
|
+
acts_as_dag_node_linked_by 'Dagnabit::Node::TestAssociations::OtherLink'
|
14
|
+
end
|
15
|
+
|
16
|
+
class BaseNode < ActiveRecord::Base
|
17
|
+
self.abstract_class = true
|
18
|
+
set_table_name 'nodes'
|
19
|
+
acts_as_dag_node_linked_by '::Link'
|
20
|
+
end
|
21
|
+
|
22
|
+
class DerivedNode < BaseNode
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup
|
26
|
+
@n1 = ::Node.new
|
27
|
+
@n2 = ::Node.new
|
28
|
+
@n3 = ::Node.new
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'report links for which the node is a child' do
|
32
|
+
::Link.connect(@n1, @n2)
|
33
|
+
|
34
|
+
assert_equal 1, @n2.links_as_child.count
|
35
|
+
assert_equal @n1, @n2.links_as_child.first.ancestor
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'report links for which the node is a child, using a different link model' do
|
39
|
+
n1 = OtherNode.new
|
40
|
+
n2 = OtherNode.new
|
41
|
+
|
42
|
+
OtherLink.connect(n1, n2)
|
43
|
+
|
44
|
+
assert_equal 1, n2.links_as_child.count
|
45
|
+
assert_equal n1, n2.links_as_child.first.ancestor
|
46
|
+
|
47
|
+
assert n2.links_as_child.first.is_a?(OtherLink), 'expected OtherLink'
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'report links for which the node is a child, using a different link model and different foreign key' do
|
51
|
+
n1 = CustomizedLinkNode.new
|
52
|
+
n2 = CustomizedLinkNode.new
|
53
|
+
|
54
|
+
CustomizedLink.connect(n1, n2)
|
55
|
+
|
56
|
+
assert_equal 1, n2.links_as_child.count
|
57
|
+
assert_equal n1, n2.links_as_child.first.ancestor
|
58
|
+
|
59
|
+
assert n2.links_as_child.first.is_a?(CustomizedLink), 'expected CustomizedLink'
|
60
|
+
end
|
61
|
+
|
62
|
+
should 'report links for which the node is a parent' do
|
63
|
+
::Link.connect(@n1, @n2)
|
64
|
+
|
65
|
+
assert_equal 1, @n1.links_as_parent.count
|
66
|
+
assert_equal @n2, @n1.links_as_parent.first.descendant
|
67
|
+
end
|
68
|
+
|
69
|
+
should 'report links for which the node is an ancestor' do
|
70
|
+
::Link.connect(@n1, @n2)
|
71
|
+
::Link.connect(@n1, @n3)
|
72
|
+
|
73
|
+
assert_equal 2, @n1.links_as_ancestor.count
|
74
|
+
assert_equal Set.new([@n2, @n3]), Set.new(@n1.links_as_ancestor.map(&:descendant))
|
75
|
+
end
|
76
|
+
|
77
|
+
should 'report links for which the node is a descendant' do
|
78
|
+
::Link.connect(@n1, @n2)
|
79
|
+
::Link.connect(@n2, @n3)
|
80
|
+
|
81
|
+
assert_equal 2, @n3.links_as_descendant.count
|
82
|
+
assert_equal Set.new([@n1, @n2]), Set.new(@n3.links_as_descendant.map(&:ancestor))
|
83
|
+
end
|
84
|
+
|
85
|
+
should 'clean up links to parents and children when destroyed' do
|
86
|
+
n1 = ::Node.new
|
87
|
+
n2 = ::Node.new
|
88
|
+
n3 = ::Node.new
|
89
|
+
|
90
|
+
::Link.connect(n1, n2)
|
91
|
+
::Link.connect(n2, n3)
|
92
|
+
::Link.connect(n1, n3)
|
93
|
+
|
94
|
+
n2.destroy
|
95
|
+
|
96
|
+
assert_equal [n3], n1.children
|
97
|
+
assert_equal [n3], n1.descendants
|
98
|
+
assert_equal [n1], n3.ancestors
|
99
|
+
assert_equal [n1], n3.parents
|
100
|
+
end
|
101
|
+
|
102
|
+
should 'report links for which the node is a parent on derived nodes' do
|
103
|
+
n1 = DerivedNode.new
|
104
|
+
n2 = DerivedNode.new
|
105
|
+
|
106
|
+
::Link.connect(n1, n2)
|
107
|
+
|
108
|
+
assert_equal [n2], n1.links_as_parent.map(&:descendant)
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'node class scoping' do
|
112
|
+
setup do
|
113
|
+
@n1 = ::Node.new
|
114
|
+
@n2a = ::BetaNode.new
|
115
|
+
@n2b = ::BetaNode.new
|
116
|
+
@n3 = ::Node.new
|
117
|
+
|
118
|
+
::Link.connect(@n1, @n2a)
|
119
|
+
::Link.connect(@n1, @n2b)
|
120
|
+
::Link.connect(@n2a, @n3)
|
121
|
+
::Link.connect(@n2b, @n3)
|
122
|
+
end
|
123
|
+
|
124
|
+
should 'apply to parent links' do
|
125
|
+
assert_equal 2, @n1.links_as_parent.length
|
126
|
+
end
|
127
|
+
|
128
|
+
should 'apply to child links' do
|
129
|
+
assert_equal 2, @n3.links_as_child.length
|
130
|
+
end
|
131
|
+
|
132
|
+
should 'apply to ancestor links' do
|
133
|
+
# We expect three links here:
|
134
|
+
# n1 -> n2a
|
135
|
+
# n1 -> n2b
|
136
|
+
# n1 -> n3
|
137
|
+
assert_equal 3, @n1.links_as_ancestor.length
|
138
|
+
end
|
139
|
+
|
140
|
+
should 'apply to descendant links' do
|
141
|
+
# Similar logic here.
|
142
|
+
assert_equal 3, @n3.links_as_descendant.length
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Node
|
5
|
+
class TestClassMethods < ActiveRecord::TestCase
|
6
|
+
def setup
|
7
|
+
#
|
8
|
+
# Structure:
|
9
|
+
#
|
10
|
+
# -- n1
|
11
|
+
# / \
|
12
|
+
# n0 n3 -- n4
|
13
|
+
# \-- n2 /
|
14
|
+
#
|
15
|
+
@links = []
|
16
|
+
@ns = (1..5).map { ::Node.new }
|
17
|
+
|
18
|
+
[ [@ns[0], @ns[1]],
|
19
|
+
[@ns[0], @ns[2]],
|
20
|
+
[@ns[1], @ns[3]],
|
21
|
+
[@ns[2], @ns[3]],
|
22
|
+
[@ns[3], @ns[4]] ].each do |ancestor, descendant|
|
23
|
+
@links << ::Link.new(:ancestor => ancestor, :descendant => descendant)
|
24
|
+
end
|
25
|
+
|
26
|
+
@ns.each { |n| n.save }
|
27
|
+
@links.each { |l| l.save }
|
28
|
+
@root = @ns[0]
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'allow retrieval of subgraphs rooted at a given node' do
|
32
|
+
graph = ::Node.subgraph_from(@root)
|
33
|
+
|
34
|
+
assert_equal Set.new(@ns), graph.nodes
|
35
|
+
assert_equal Set.new(@links), graph.edges
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'allow retrieval of subgraphs rooted at multiple nodes' do
|
39
|
+
graph = ::Node.subgraph_from(@ns[1], @ns[2])
|
40
|
+
|
41
|
+
# expect n0 to not be present
|
42
|
+
assert_equal Set.new(@ns[1..4]), graph.nodes
|
43
|
+
|
44
|
+
# expect (n0, n1) and (n0, n2) to not be present
|
45
|
+
assert_equal Set.new(@links[2..-1]), graph.edges
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Node
|
5
|
+
class TestConfiguration < ActiveRecord::TestCase
|
6
|
+
def setup
|
7
|
+
@node_model = Class.new(ActiveRecord::Base) do
|
8
|
+
set_table_name 'nodes'
|
9
|
+
|
10
|
+
extend Dagnabit::Node::Configuration
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'accept the name of a link class' do
|
15
|
+
@node_model.configure_acts_as_dag_node('Link')
|
16
|
+
|
17
|
+
assert_equal 'Link', @node_model.link_class_name
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'be inheritable' do
|
21
|
+
@node_model.configure_acts_as_dag_node('Link')
|
22
|
+
|
23
|
+
subclassed_model = Class.new(@node_model)
|
24
|
+
|
25
|
+
assert_equal 'Link', subclassed_model.link_class_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Dagnabit
|
4
|
+
module Node
|
5
|
+
class TestNeighbors < ActiveRecord::TestCase
|
6
|
+
class OtherNode < ActiveRecord::Base
|
7
|
+
set_table_name 'nodes'
|
8
|
+
acts_as_dag_node_linked_by '::Link'
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@n1 = ::Node.new
|
13
|
+
@n2 = OtherNode.new
|
14
|
+
@n3 = ::Node.new
|
15
|
+
|
16
|
+
::Link.connect(@n1, @n2)
|
17
|
+
::Link.connect(@n2, @n3)
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'return all ancestors' do
|
21
|
+
ancestors = @n3.ancestors
|
22
|
+
assert_equal 2, ancestors.length
|
23
|
+
assert_equal Set.new([@n1, @n2]), Set.new(ancestors)
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'return all ancestors of a specified type' do
|
27
|
+
ancestors = @n3.ancestors_of_type(OtherNode.name)
|
28
|
+
assert_equal [@n2], ancestors
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'return all descendants' do
|
32
|
+
descendants = @n1.descendants
|
33
|
+
assert_equal 2, descendants.length
|
34
|
+
assert_equal Set.new([@n2, @n3]), Set.new(descendants)
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'return all descendants of a specified type' do
|
38
|
+
descendants = @n1.descendants_of_type(OtherNode.name)
|
39
|
+
assert_equal [@n2], descendants
|
40
|
+
end
|
41
|
+
|
42
|
+
should 'return all children' do
|
43
|
+
assert_equal [@n2], @n1.children
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'return all children of a specified type' do
|
47
|
+
assert_equal [@n2], @n1.children_of_type(OtherNode.name)
|
48
|
+
assert_equal [], @n1.children_of_type(::Node.name)
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'return all parents' do
|
52
|
+
assert_equal [@n1], @n2.parents
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'return all parents of a specified type' do
|
56
|
+
assert_equal [@n2], @n3.parents_of_type(OtherNode.name)
|
57
|
+
assert_equal [], @n3.parents_of_type(::Node.name)
|
58
|
+
end
|
59
|
+
|
60
|
+
should 'not report ancestor nodes as parent nodes' do
|
61
|
+
assert_equal 1, @n3.parents.length
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'return all ancestors of the specified neighbor types using customized links' do
|
65
|
+
n1 = CustomizedLinkNode.new
|
66
|
+
n2 = CustomizedLinkNode.new
|
67
|
+
n3 = CustomizedLinkNode.new
|
68
|
+
|
69
|
+
CustomizedLink.connect(n1, n2)
|
70
|
+
CustomizedLink.connect(n2, n3)
|
71
|
+
|
72
|
+
ancestors = n3.ancestors
|
73
|
+
assert_equal 2, ancestors.length
|
74
|
+
assert_equal Set.new([n1, n2]), Set.new(ancestors)
|
75
|
+
end
|
76
|
+
|
77
|
+
should 'return all descendants of the specified neighbor types using customized links' do
|
78
|
+
n1 = CustomizedLinkNode.new
|
79
|
+
n2 = CustomizedLinkNode.new
|
80
|
+
n3 = CustomizedLinkNode.new
|
81
|
+
|
82
|
+
CustomizedLink.connect(n1, n2)
|
83
|
+
CustomizedLink.connect(n2, n3)
|
84
|
+
|
85
|
+
descendants = n1.descendants
|
86
|
+
assert_equal 2, descendants.length
|
87
|
+
assert_equal Set.new([n2, n3]), Set.new(descendants)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
require 'active_record'
|
6
|
+
require 'active_record/fixtures'
|
7
|
+
require 'active_support'
|
8
|
+
require 'active_support/whiny_nil'
|
9
|
+
require 'connection'
|
10
|
+
|
11
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
12
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
13
|
+
require 'dagnabit'
|
14
|
+
|
15
|
+
SCHEMA_ROOT = File.join(File.dirname(__FILE__), 'schema')
|
16
|
+
|
17
|
+
class ActiveSupport::TestCase
|
18
|
+
include ActiveRecord::TestFixtures
|
19
|
+
|
20
|
+
self.use_transactional_fixtures = true
|
21
|
+
end
|
22
|
+
|
23
|
+
load(SCHEMA_ROOT + '/schema.rb')
|
24
|
+
|
25
|
+
# load link models before other models
|
26
|
+
models = Dir[File.dirname(__FILE__) + '/models/**/*.rb']
|
27
|
+
models.sort { |x, y| x =~ /link\.rb$/ ? -1 : 0 }.each { |m| require m }
|
data/test/models/link.rb
ADDED
data/test/models/node.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table :edges, :force => true do |t|
|
3
|
+
t.integer :ancestor_id
|
4
|
+
t.integer :descendant_id
|
5
|
+
t.string :ancestor_type
|
6
|
+
t.string :descendant_type
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :edges_transitive_closure_tuples, :force => true do |t|
|
10
|
+
t.integer :ancestor_id
|
11
|
+
t.integer :descendant_id
|
12
|
+
t.string :ancestor_type
|
13
|
+
t.string :descendant_type
|
14
|
+
end
|
15
|
+
|
16
|
+
create_table :custom_data_edges, :force => true do |t|
|
17
|
+
t.integer :ancestor_id
|
18
|
+
t.integer :descendant_id
|
19
|
+
t.string :ancestor_type
|
20
|
+
t.string :descendant_type
|
21
|
+
t.string :data
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table :custom_data_edges_transitive_closure_tuples, :force => true do |t|
|
25
|
+
t.integer :ancestor_id
|
26
|
+
t.integer :descendant_id
|
27
|
+
t.string :ancestor_type
|
28
|
+
t.string :descendant_type
|
29
|
+
t.string :data
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :nodes, :force => true do |t|
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table :beta_nodes, :force => true do |t|
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table :other_name_edges, :force => true do |t|
|
39
|
+
t.integer :the_ancestor_id
|
40
|
+
t.integer :the_descendant_id
|
41
|
+
t.string :ancestor_type
|
42
|
+
t.string :descendant_type
|
43
|
+
end
|
44
|
+
|
45
|
+
create_table :my_transitive_closure, :force => true do |t|
|
46
|
+
t.integer :the_ancestor_id
|
47
|
+
t.integer :the_descendant_id
|
48
|
+
t.string :ancestor_type
|
49
|
+
t.string :descendant_type
|
50
|
+
end
|
51
|
+
end
|