dagnabit 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.autotest +5 -0
  2. data/.document +5 -0
  3. data/.gitignore +6 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +186 -0
  6. data/Rakefile +69 -0
  7. data/VERSION.yml +5 -0
  8. data/bin/dagnabit-test +53 -0
  9. data/dagnabit.gemspec +124 -0
  10. data/init.rb +1 -0
  11. data/lib/dagnabit.rb +17 -0
  12. data/lib/dagnabit/activation.rb +60 -0
  13. data/lib/dagnabit/link/associations.rb +18 -0
  14. data/lib/dagnabit/link/class_methods.rb +43 -0
  15. data/lib/dagnabit/link/configuration.rb +40 -0
  16. data/lib/dagnabit/link/cycle_prevention.rb +31 -0
  17. data/lib/dagnabit/link/named_scopes.rb +65 -0
  18. data/lib/dagnabit/link/transitive_closure_link_model.rb +86 -0
  19. data/lib/dagnabit/link/transitive_closure_recalculation.rb +17 -0
  20. data/lib/dagnabit/link/transitive_closure_recalculation/on_create.rb +104 -0
  21. data/lib/dagnabit/link/transitive_closure_recalculation/on_destroy.rb +125 -0
  22. data/lib/dagnabit/link/transitive_closure_recalculation/on_update.rb +13 -0
  23. data/lib/dagnabit/link/transitive_closure_recalculation/utilities.rb +56 -0
  24. data/lib/dagnabit/link/validations.rb +26 -0
  25. data/lib/dagnabit/node/associations.rb +84 -0
  26. data/lib/dagnabit/node/class_methods.rb +74 -0
  27. data/lib/dagnabit/node/configuration.rb +26 -0
  28. data/lib/dagnabit/node/neighbors.rb +73 -0
  29. data/test/connections/native_postgresql/connection.rb +17 -0
  30. data/test/connections/native_sqlite3/connection.rb +24 -0
  31. data/test/dagnabit/link/test_associations.rb +61 -0
  32. data/test/dagnabit/link/test_class_methods.rb +102 -0
  33. data/test/dagnabit/link/test_configuration.rb +38 -0
  34. data/test/dagnabit/link/test_cycle_prevention.rb +64 -0
  35. data/test/dagnabit/link/test_named_scopes.rb +32 -0
  36. data/test/dagnabit/link/test_transitive_closure_link_model.rb +69 -0
  37. data/test/dagnabit/link/test_transitive_closure_recalculation.rb +139 -0
  38. data/test/dagnabit/link/test_validations.rb +39 -0
  39. data/test/dagnabit/node/test_associations.rb +147 -0
  40. data/test/dagnabit/node/test_class_methods.rb +49 -0
  41. data/test/dagnabit/node/test_configuration.rb +29 -0
  42. data/test/dagnabit/node/test_neighbors.rb +91 -0
  43. data/test/helper.rb +27 -0
  44. data/test/models/beta_node.rb +3 -0
  45. data/test/models/custom_data_link.rb +4 -0
  46. data/test/models/customized_link.rb +7 -0
  47. data/test/models/customized_link_node.rb +4 -0
  48. data/test/models/link.rb +4 -0
  49. data/test/models/node.rb +3 -0
  50. data/test/schema/schema.rb +51 -0
  51. 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
@@ -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 }
@@ -0,0 +1,3 @@
1
+ class BetaNode < ActiveRecord::Base
2
+ acts_as_dag_node_linked_by 'Link'
3
+ end
@@ -0,0 +1,4 @@
1
+ class CustomDataLink < ActiveRecord::Base
2
+ set_table_name 'custom_data_edges'
3
+ acts_as_dag_link
4
+ end
@@ -0,0 +1,7 @@
1
+ class CustomizedLink < ActiveRecord::Base
2
+ set_table_name 'other_name_edges'
3
+
4
+ acts_as_dag_link :ancestor_id_column => 'the_ancestor_id',
5
+ :descendant_id_column => 'the_descendant_id',
6
+ :transitive_closure_table_name => 'my_transitive_closure'
7
+ end
@@ -0,0 +1,4 @@
1
+ class CustomizedLinkNode < ActiveRecord::Base
2
+ set_table_name 'nodes'
3
+ acts_as_dag_node_linked_by '::CustomizedLink'
4
+ end
@@ -0,0 +1,4 @@
1
+ class Link < ActiveRecord::Base
2
+ set_table_name 'edges'
3
+ acts_as_dag_link
4
+ end
@@ -0,0 +1,3 @@
1
+ class Node < ActiveRecord::Base
2
+ acts_as_dag_node_linked_by 'Link'
3
+ end
@@ -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