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.
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