dagnabit 2.2.6 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/History.md +105 -0
  2. data/LICENSE +1 -1
  3. data/MIGRATION.md +56 -0
  4. data/README.md +222 -0
  5. data/bin/dagnabit-test +11 -31
  6. data/db/connection.rb +3 -0
  7. data/{test/connections/native_postgresql → db/connections/postgresql}/connection.rb +6 -5
  8. data/db/models/edge.rb +6 -0
  9. data/db/models/other_edge.rb +6 -0
  10. data/db/models/other_vertex.rb +6 -0
  11. data/db/models/vertex.rb +6 -0
  12. data/db/schema.rb +32 -0
  13. data/lib/dagnabit.rb +6 -19
  14. data/lib/dagnabit/edge.rb +7 -0
  15. data/lib/dagnabit/edge/activation.rb +14 -0
  16. data/lib/dagnabit/edge/associations.rb +10 -0
  17. data/lib/dagnabit/edge/connectivity.rb +38 -0
  18. data/lib/dagnabit/graph.rb +143 -0
  19. data/lib/dagnabit/migration.rb +98 -0
  20. data/lib/dagnabit/version.rb +3 -0
  21. data/lib/dagnabit/vertex.rb +10 -0
  22. data/lib/dagnabit/vertex/activation.rb +19 -0
  23. data/lib/dagnabit/vertex/associations.rb +24 -0
  24. data/lib/dagnabit/vertex/bonding.rb +43 -0
  25. data/lib/dagnabit/vertex/connectivity.rb +130 -0
  26. data/lib/dagnabit/vertex/neighbors.rb +50 -0
  27. data/lib/dagnabit/vertex/settings.rb +56 -0
  28. metadata +94 -143
  29. data/.autotest +0 -5
  30. data/.document +0 -5
  31. data/.gitignore +0 -7
  32. data/Gemfile +0 -15
  33. data/Gemfile.lock +0 -38
  34. data/History.txt +0 -81
  35. data/README.rdoc +0 -202
  36. data/Rakefile +0 -52
  37. data/VERSION.yml +0 -5
  38. data/dagnabit.gemspec +0 -142
  39. data/init.rb +0 -1
  40. data/lib/dagnabit/activation.rb +0 -60
  41. data/lib/dagnabit/link/associations.rb +0 -18
  42. data/lib/dagnabit/link/class_methods.rb +0 -43
  43. data/lib/dagnabit/link/configuration.rb +0 -40
  44. data/lib/dagnabit/link/cycle_prevention.rb +0 -31
  45. data/lib/dagnabit/link/named_scopes.rb +0 -65
  46. data/lib/dagnabit/link/transitive_closure_link_model.rb +0 -86
  47. data/lib/dagnabit/link/transitive_closure_recalculation.rb +0 -17
  48. data/lib/dagnabit/link/transitive_closure_recalculation/on_create.rb +0 -104
  49. data/lib/dagnabit/link/transitive_closure_recalculation/on_destroy.rb +0 -125
  50. data/lib/dagnabit/link/transitive_closure_recalculation/on_update.rb +0 -17
  51. data/lib/dagnabit/link/transitive_closure_recalculation/utilities.rb +0 -56
  52. data/lib/dagnabit/link/validations.rb +0 -26
  53. data/lib/dagnabit/node/associations.rb +0 -84
  54. data/lib/dagnabit/node/class_methods.rb +0 -74
  55. data/lib/dagnabit/node/configuration.rb +0 -26
  56. data/lib/dagnabit/node/neighbors.rb +0 -73
  57. data/test/connections/native_sqlite3/connection.rb +0 -24
  58. data/test/dagnabit/link/test_associations.rb +0 -61
  59. data/test/dagnabit/link/test_class_methods.rb +0 -102
  60. data/test/dagnabit/link/test_configuration.rb +0 -38
  61. data/test/dagnabit/link/test_cycle_prevention.rb +0 -64
  62. data/test/dagnabit/link/test_named_scopes.rb +0 -32
  63. data/test/dagnabit/link/test_transitive_closure_link_model.rb +0 -69
  64. data/test/dagnabit/link/test_transitive_closure_recalculation.rb +0 -178
  65. data/test/dagnabit/link/test_validations.rb +0 -39
  66. data/test/dagnabit/node/test_associations.rb +0 -147
  67. data/test/dagnabit/node/test_class_methods.rb +0 -49
  68. data/test/dagnabit/node/test_configuration.rb +0 -29
  69. data/test/dagnabit/node/test_neighbors.rb +0 -91
  70. data/test/helper.rb +0 -26
  71. data/test/models/beta_node.rb +0 -3
  72. data/test/models/custom_data_link.rb +0 -4
  73. data/test/models/customized_link.rb +0 -7
  74. data/test/models/customized_link_node.rb +0 -4
  75. data/test/models/link.rb +0 -4
  76. data/test/models/node.rb +0 -3
  77. data/test/schema/schema.rb +0 -51
@@ -1,125 +0,0 @@
1
- module Dagnabit
2
- module Link
3
- module TransitiveClosureRecalculation
4
- module OnDestroy
5
- def after_destroy
6
- super
7
- update_transitive_closure_for_destroy(*quoted_dag_link_values)
8
- end
9
-
10
- private
11
-
12
- def update_transitive_closure_for_destroy(aid, did, atype, dtype)
13
- my_table = self.class.quoted_table_name
14
- my_aid, my_did, my_atype, my_dtype = quoted_dag_link_column_names
15
- tc = self.class.transitive_closure_table_name
16
- tc_aid, tc_did, tc_atype, tc_dtype = quoted_dag_link_column_names
17
-
18
- with_temporary_edge_tables('suspect', 'trusty', 'new') do |suspect, trusty, new|
19
- connection.execute <<-END
20
- INSERT INTO #{suspect} (#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype})
21
- SELECT * FROM (
22
- SELECT
23
- TC1.#{tc_aid}, TC2.#{tc_did}, TC1.#{tc_atype}, TC2.#{tc_dtype}
24
- FROM
25
- #{tc} AS TC1, #{tc} AS TC2
26
- WHERE
27
- TC1.#{tc_did} = #{aid} AND TC2.#{tc_aid} = #{did}
28
- AND
29
- TC1.#{tc_dtype} = #{atype} AND TC2.#{tc_atype} = #{dtype}
30
- UNION
31
- SELECT
32
- TC.#{tc_aid}, #{did}, TC.#{tc_atype}, #{dtype}
33
- FROM
34
- #{tc} AS TC
35
- WHERE
36
- TC.#{tc_did} = #{aid} AND TC.#{tc_dtype} = #{atype}
37
- UNION
38
- SELECT
39
- #{aid}, TC.#{tc_did}, #{atype}, TC.#{tc_dtype}
40
- FROM
41
- #{tc} AS TC
42
- WHERE
43
- TC.#{tc_aid} = #{did} AND TC.#{tc_atype} = #{dtype}
44
- UNION
45
- SELECT
46
- #{aid}, #{did}, #{atype}, #{dtype}
47
- FROM
48
- #{tc} AS TC
49
- WHERE
50
- TC.#{tc_aid} = #{aid} AND TC.#{tc_did} = #{did}
51
- AND
52
- TC.#{tc_atype} = #{atype} AND TC.#{tc_dtype} = #{dtype}
53
- ) AS tmp0
54
- END
55
-
56
- connection.execute <<-END
57
- INSERT INTO #{trusty} (#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype})
58
- SELECT
59
- #{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype}
60
- FROM (
61
- SELECT
62
- #{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype}
63
- FROM
64
- #{tc} AS TC
65
- WHERE NOT EXISTS (
66
- SELECT *
67
- FROM
68
- #{suspect} AS SUSPECT
69
- WHERE
70
- SUSPECT.#{tc_aid} = TC.#{tc_aid} AND SUSPECT.#{tc_did} = TC.#{tc_did}
71
- AND
72
- SUSPECT.#{tc_atype} = TC.#{tc_atype} AND SUSPECT.#{tc_dtype} = TC.#{tc_dtype}
73
- )
74
- UNION
75
- SELECT
76
- #{my_aid}, #{my_did}, #{my_atype}, #{my_dtype}
77
- FROM
78
- #{my_table} AS G
79
- WHERE
80
- NOT (G.#{my_aid} = #{aid} AND g.#{my_atype} = #{atype}
81
- AND
82
- G.#{my_did} = #{did} AND g.#{my_dtype} = #{dtype})
83
- ) AS tmp0
84
- END
85
-
86
- connection.execute <<-END
87
- INSERT INTO #{new} (#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype})
88
- SELECT * FROM (
89
- SELECT #{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype} FROM #{trusty}
90
- UNION
91
- SELECT
92
- T1.#{tc_aid}, T2.#{tc_aid}, T1.#{tc_atype}, T2.#{tc_dtype}
93
- FROM
94
- #{trusty} T1, #{trusty} T2
95
- WHERE
96
- T1.#{tc_aid} = T2.#{tc_aid} AND T1.#{tc_atype} = T2.#{tc_dtype}
97
- UNION
98
- SELECT
99
- T1.#{tc_aid}, T3.#{tc_aid}, T1.#{tc_atype}, T3.#{tc_dtype}
100
- FROM
101
- #{trusty} T1, #{trusty} T2, #{trusty} T3
102
- WHERE
103
- T1.#{tc_aid} = T2.#{tc_aid} AND T2.#{tc_aid} = T3.#{tc_aid}
104
- AND
105
- T1.#{tc_dtype} = T2.#{tc_atype} AND T2.#{tc_dtype} = T3.#{tc_atype}
106
- ) AS tmp0
107
- END
108
-
109
- connection.execute <<-END
110
- DELETE FROM #{tc} WHERE NOT EXISTS (
111
- SELECT *
112
- FROM
113
- #{new} T
114
- WHERE
115
- T.#{tc_aid} = #{tc}.#{tc_aid} AND T.#{tc_did} = #{tc}.#{tc_did}
116
- AND
117
- T.#{tc_atype} = #{tc}.#{tc_atype} AND T.#{tc_dtype} = #{tc}.#{tc_dtype}
118
- )
119
- END
120
- end
121
- end
122
- end
123
- end
124
- end
125
- end
@@ -1,17 +0,0 @@
1
- module Dagnabit
2
- module Link
3
- module TransitiveClosureRecalculation
4
- module OnUpdate
5
- def after_update
6
- current_values = dag_link_column_names.map { |n| connection.quote(send(n)) }
7
- old_values = dag_link_column_names.map { |n| connection.quote(changes[n].try(:first) || send(n)) }
8
-
9
- return unless current_values != old_values
10
-
11
- update_transitive_closure_for_destroy(*old_values)
12
- update_transitive_closure_for_create
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,56 +0,0 @@
1
- module Dagnabit
2
- module Link
3
- module TransitiveClosureRecalculation
4
- module Utilities
5
- private
6
-
7
- def quoted_dag_link_values
8
- dag_link_column_names.map { |n| connection.quote(send(n)) }
9
- end
10
-
11
- def quoted_dag_link_column_names
12
- dag_link_column_names.map { |n| connection.quote_column_name(n) }
13
- end
14
-
15
- def dag_link_column_names
16
- [ self.class.ancestor_id_column,
17
- self.class.descendant_id_column,
18
- self.class.ancestor_type_column,
19
- self.class.descendant_type_column ]
20
- end
21
-
22
- def all_quoted_column_values
23
- all_column_names.map { |n| connection.quote(send(n)) }
24
- end
25
-
26
- def all_quoted_column_names
27
- all_column_names.map { |n| connection.quote_column_name(n) }
28
- end
29
-
30
- def all_column_names
31
- all_columns.map { |c| c.name }
32
- end
33
-
34
- def with_temporary_edge_tables(*tables, &block)
35
- tables.each do |table|
36
- connection.create_table(table, :temporary => true, :id => false) do |t|
37
- all_columns.each do |c|
38
- t.send(c.type, c.name)
39
- end
40
- end
41
- end
42
-
43
- yield(tables.map { |t| connection.quote_table_name(t) })
44
-
45
- tables.each do |table|
46
- connection.drop_table table
47
- end
48
- end
49
-
50
- def all_columns
51
- self.class.columns.reject { |c| c.name == 'id' || c.name == 'type' }
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,26 +0,0 @@
1
- module Dagnabit
2
- module Link
3
- #
4
- # Basic validations on link models.
5
- #
6
- # This module only installs ancestor and descendant presence validations;
7
- # the only basic requirement for a link is that it have a valid start point
8
- # and a valid end point. We validate the presence of the +ancestor+ and
9
- # +descendant+, instead of +ancestor_id+ and +descendant_id+, in order to
10
- # permit scenarios like this:
11
- #
12
- # n1 = Node.new
13
- # n2 = Node.new
14
- # l = Link.new(:ancestor => n1, :descendant => n2)
15
- # l.save # will save l, n1, and n2
16
- #
17
- module Validations
18
- def self.extended(base)
19
- base.send(:validates_presence_of, :ancestor)
20
- base.send(:validates_presence_of, :descendant)
21
- base.send(:validates_associated, :ancestor)
22
- base.send(:validates_associated, :descendant)
23
- end
24
- end
25
- end
26
- end
@@ -1,84 +0,0 @@
1
- module Dagnabit
2
- module Node
3
- #
4
- # Association macros added to node in a dagnabit dag.
5
- #
6
- # == Added associations
7
- #
8
- # * +links_as_parent+: Links for which this node is a parent.
9
- # * +links_as_child+: Links for which this node is a child.
10
- # * +links_as_ancestor+: Links for which this node is an ancestor.
11
- # * +links_as_descendant+: Links for which this node is a descendant.
12
- #
13
- # == Illustration
14
- #
15
- # Suppose we have the following graph:
16
- #
17
- # n1
18
- # |
19
- # / \
20
- # n2 n3
21
- # \ /
22
- # n4
23
- #
24
- # Here are some example queries and outputs:
25
- #
26
- # n1.links_as_parent # => [#<Link ancestor=n1, descendant=n2>, #<Link ancestor=n1, descendant=n3>]
27
- # n4.links_as_child # => [#<Link ancestor=n2, descendant=n4>, #<Link ancestor=n3, descendant=n4>]
28
- # n1.links_as_ancestor # => [#<Link ancestor=n1, descendant=n2>, #<Link ancestor=n1, descendant=n3>, #<Link ancestor=n1, descendant=n4>]
29
- # n4.links_as_descendant # => [#<Link ancestor=n2, descendant=n4>, #<Link ancestor=n3, descendant=n4>, #<Link ancestor=n1, descendant=n4>]
30
- #
31
- # In this example, we used +Link+ as the class for all links. This isn't
32
- # actually what you get back (see Dagnabit::Link for details), but the
33
- # objects you get back _will_ have ancestor and descendant accessors.
34
- #
35
- module Associations
36
- #
37
- # Installs associations on the node model.
38
- #
39
- def self.extended(base)
40
- base.install_associations
41
- end
42
-
43
- def install_associations
44
- klass = self
45
- link_class = klass.link_class_name.constantize
46
-
47
- klass.send(:has_many,
48
- :links_as_parent,
49
- :class_name => klass.link_class_name,
50
- :foreign_key => link_class.ancestor_id_column,
51
- :conditions => { link_class.ancestor_type_column => klass.name },
52
- :dependent => :destroy)
53
-
54
- klass.send(:has_many,
55
- :links_as_child,
56
- :class_name => klass.link_class_name,
57
- :foreign_key => link_class.descendant_id_column,
58
- :conditions => { link_class.descendant_type_column => klass.name },
59
- :dependent => :destroy)
60
-
61
- klass.send(:has_many,
62
- :links_as_ancestor,
63
- :class_name => link_class.transitive_closure_class.name,
64
- :foreign_key => link_class.ancestor_id_column,
65
- :conditions => { link_class.ancestor_type_column => klass.name },
66
- :readonly => true)
67
-
68
- klass.send(:has_many,
69
- :links_as_descendant,
70
- :class_name => link_class.transitive_closure_class.name,
71
- :foreign_key => link_class.descendant_id_column,
72
- :conditions => { link_class.descendant_type_column => klass.name },
73
- :readonly => true)
74
- end
75
-
76
- private
77
-
78
- def inherited(subclass)
79
- super(subclass)
80
- subclass.install_associations
81
- end
82
- end
83
- end
84
- end
@@ -1,74 +0,0 @@
1
- require 'ostruct'
2
-
3
- module Dagnabit
4
- module Node
5
- module ClassMethods
6
- #
7
- # Returns a subgraph rooted at a given set of nodes.
8
- #
9
- # While you can retrieve all descendants of a node pretty easily (i.e.
10
- # +node.descendants+), and all links from a node really easily (i.e.
11
- # +node.links_as_ancestor), it's not quite as straightforward to get the
12
- # nodes and edges (direct edges, that is) out of a graph.
13
- #
14
- # The subgraph is returned as an object with two properties: +nodes+ and
15
- # +edges+. Both properties are instances of the Set class.
16
- #
17
- # == Examples
18
- #
19
- # In the following examples, the node class is called +Node+. Variables
20
- # of the form +nM+, where M is an integer, denote Node instances.
21
- #
22
- # Retrieve all descendants of n1 and all direct edges "underneath" n1
23
- # (i.e. n1 to its children and direct edges for each of n1's
24
- # descendants):
25
- #
26
- # <pre>
27
- # Node.subgraph_from(n1)
28
- #
29
- # => #<Graph nodes=... edges=...>
30
- # </pre>
31
- #
32
- # Same as above, but builds a graph using n1 and n2 as roots.
33
- #
34
- # <pre>
35
- # Node.subgraph_from(n1, n2)
36
- # </pre>
37
- #
38
- # == Usage tip
39
- #
40
- # +subgraph_from+ forces loading of the +descendants+ and
41
- # +links_as_parent+ +links_as_parent+ associations on Node. In some
42
- # situations, you may experience better performance if you use
43
- # ActiveRecord's eager-loading capabilities when using subgraph_from:
44
- #
45
- # <pre>
46
- # roots = Node.find(..., :include => [:descendants, :links_as_parent])
47
- # Node.subgraph_from(roots)
48
- # </pre>
49
- #
50
- def subgraph_from(*roots)
51
- OpenStruct.new.tap do |g|
52
- g.nodes = all_nodes_of(roots)
53
- g.edges = direct_edges_of(roots)
54
- end
55
- end
56
-
57
- private
58
-
59
- def all_nodes_of(roots)
60
- roots.inject(Set.new) do |r, root|
61
- r += root.descendants
62
- r << root
63
- end
64
- end
65
-
66
- def direct_edges_of(roots)
67
- roots.inject(Set.new) do |r, root|
68
- r += root.links_as_ancestor.find(:all, :include => { :descendant => :links_as_parent }).map { |l| l.descendant.links_as_parent }.flatten
69
- r += root.links_as_parent
70
- end
71
- end
72
- end
73
- end
74
- end
@@ -1,26 +0,0 @@
1
- module Dagnabit
2
- module Node
3
- #
4
- # Configuration mechanism for nodes in a dagnabit-managed dag.
5
- #
6
- module Configuration
7
- #
8
- # Writes accessors for configuration data into a node.
9
- #
10
- # The following accessors are available:
11
- # [link_class_name]
12
- # The name of the model used to link nodes of this class.
13
- #
14
- def self.extended(base)
15
- base.class_inheritable_accessor :link_class_name
16
- end
17
-
18
- #
19
- # Configure node behavior.
20
- #
21
- def configure_acts_as_dag_node(link_class_name)
22
- self.link_class_name = link_class_name
23
- end
24
- end
25
- end
26
- end
@@ -1,73 +0,0 @@
1
- module Dagnabit
2
- module Node
3
- #
4
- # Instance methods for finding out the neighbors of a node.
5
- #
6
- # These methods do _not_ behave like association proxies: they're just
7
- # wrappers around <tt>ActiveRecord::Base#find</tt>. Therefore, they do not
8
- # cache, do not support calculations, do not support extension modules,
9
- # named scopes, etc.
10
- #
11
- # These methods aren't association proxies because a link's ancestor and
12
- # descendant are polymorphic associations, and ActiveRecord does not
13
- # support polymorphic has_many :through associations.
14
- #
15
- module Neighbors
16
- #
17
- # Finds the parents (immediate predecessors) of this node.
18
- #
19
- def parents
20
- links_as_child.find(:all, :include => :ancestor).map { |l| l.ancestor }
21
- end
22
-
23
- #
24
- # Finds the parents (immediate predecessors) of this node satisfying a given type.
25
- #
26
- def parents_of_type(type)
27
- links_as_child.ancestor_type(type).find(:all, :include => :ancestor).map { |l| l.ancestor }
28
- end
29
-
30
- #
31
- # Finds the children (immediate successors) of this node.
32
- #
33
- def children
34
- links_as_parent.find(:all, :include => :descendant).map { |l| l.descendant }
35
- end
36
-
37
- #
38
- # Finds the children (immediate successors) of this node satisfying a given type.
39
- #
40
- def children_of_type(type)
41
- links_as_parent.descendant_type(type).find(:all, :include => :descendant).map { |l| l.descendant }
42
- end
43
-
44
- #
45
- # Find the ancestors (predecessors) of this node.
46
- #
47
- def ancestors
48
- links_as_descendant.find(:all, :include => :ancestor).map { |l| l.ancestor }
49
- end
50
-
51
- #
52
- # Find the ancestors (predecessors) of this node satisfying a given type.
53
- #
54
- def ancestors_of_type(type)
55
- links_as_descendant.ancestor_type(type).find(:all, :include => :ancestor).map { |l| l.ancestor }
56
- end
57
-
58
- #
59
- # Finds the descendants (successors) of this node.
60
- #
61
- def descendants
62
- links_as_ancestor.find(:all, :include => :descendant).map { |l| l.descendant }
63
- end
64
-
65
- #
66
- # Finds the descendants (successors) of this node satisfying a given type.
67
- #
68
- def descendants_of_type(type)
69
- links_as_ancestor.descendant_type(type).find(:all, :include => :descendant).map { |l| l.descendant }
70
- end
71
- end
72
- end
73
- end