dagnabit 2.2.6 → 3.0.0

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 (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
@@ -0,0 +1,105 @@
1
+ 3.0.0 2011-01-10
2
+ ================
3
+
4
+ Major changes
5
+ -------------
6
+
7
+ * Completely new data model: no more post-insert processing, no more transitive
8
+ closure table. Downside: only PostgreSQL is presently supported.
9
+
10
+ 2.2.6 2010-01-01
11
+ ================
12
+
13
+ Bugfixes
14
+ --------
15
+
16
+ * Fixed version requirements.
17
+
18
+ 2.2.5 2010-10-01
19
+ ================
20
+
21
+ Minor changes
22
+ -------------
23
+
24
+ * Ported development code to Bundler 1.0.
25
+ * Removed usage of `Object#returning`, which was deprecated in ActiveSupport
26
+ 2.3.9.
27
+
28
+ 2.2.4 2010-03-15
29
+ ================
30
+
31
+ Bugfixes
32
+ --------
33
+
34
+ * Ensure that ActiveRecord is loaded before dagnabit's extensions are loaded.
35
+ This fixes a problem that would occasionally occur when using dagnabit in
36
+ Bundler.
37
+
38
+ 2.2.3 2010-02-17
39
+ ================
40
+
41
+ Bugfixes
42
+ --------
43
+
44
+ * It was not possible to delete links having custom data attributes in dagnabit
45
+ versions <= 2.3.2. This has been fixed.
46
+
47
+ 2.2.2 2010-02-04
48
+ ================
49
+
50
+ Minor enhancements
51
+ ------------------
52
+
53
+ * Slightly smarter transitive closure maintenance: if a link was saved but
54
+ neither its ancestor nor descendant changed, the transitive closure will not
55
+ be recalculated.
56
+ * Removed usage of deprecated `require 'activesupport'`.
57
+
58
+ 2.2.1 2009-12-07
59
+ ================
60
+
61
+ Minor enhancements
62
+ ------------------
63
+
64
+ * Added NUBIC provenance.
65
+
66
+ 2.2.0 2009-09-14
67
+ ================
68
+
69
+ Minor enhancements
70
+ ------------------
71
+
72
+ * Commonized models used in dagnabit's tests.
73
+ * Switched to named scopes to implement Link#linking and TransitiveClosureLink#linking.
74
+
75
+ 2.1.0 2009-09-04
76
+ ================
77
+
78
+ Bugfixes
79
+ --------
80
+
81
+ * Fixed overzealous on-destroy transitive closure maintenance in situations like these:
82
+
83
+ <pre>
84
+ n1 -> n2 -> n3
85
+ |__________|
86
+ </pre>
87
+
88
+ Previously, deleting the edges (n1, n2) and (n2, n3) would also delete the
89
+ edge (n1, n3).
90
+
91
+ 2.0.0 2009-08-17
92
+ ================
93
+
94
+ Major changes
95
+ -------------
96
+
97
+ * Link models now validate their ancestor and descendant. This means that
98
+ Link#connect will fail if a link cannot be made due to node validation
99
+ failures.
100
+
101
+ Unfortunately, this also means you can no longer write (say)
102
+ `node1.links_as_parent.build(:descendant => node2)` or
103
+ `node2.links_as_child.build(:ancestor => node1)`.
104
+
105
+ See commit `61d6e3841b63fdcaad7fcecd05b24f4b9f217ba2` for more details.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 David Yip
1
+ Copyright (c) 2009, 2010 David Yip
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,56 @@
1
+ Migrating from dagnabit 2 to dagnabit 3
2
+ =======================================
3
+
4
+ 1. Polymorphic associations are no longer supported. If you need a polymorphic
5
+ graph, convert your data model to use single-table inheritance.
6
+
7
+ 2. The `*_transitive_closure` tables are no longer used, and can be dropped.
8
+
9
+ 3. dagnabit is no longer mixed into `ActiveRecord::Base` by default. You need
10
+ to augment the models you want to use as vertices:
11
+
12
+ class Vertex < ActiveRecord::Base
13
+ extend Dagnabit::Vertex::Activation
14
+
15
+ acts_as_vertex
16
+ end
17
+
18
+ 4. The default name of the edge table is now `edges`, and the name of the vertex
19
+ table is taken from the model.
20
+
21
+ So, for example, if you had the following models:
22
+
23
+ class Vertex < ActiveRecord::Base
24
+ extend Dagnabit::Vertex::Activation
25
+
26
+ acts_as_vertex
27
+ end
28
+
29
+ class BetaVertex < Vertex
30
+ end
31
+
32
+ class DifferentGraphVertex < ActiveRecord::Base
33
+ extend Dagnabit::Vertex::Activation
34
+
35
+ acts_as_vertex
36
+ set_edge_table 'different_edges'
37
+ end
38
+
39
+ then `Vertex` and `BetaVertex` would be stored in the `vertices` table and use
40
+ the `edges` table, and `DifferentGraphVertex` instances would be created from rows
41
+ in the `different_graph_vertices` table and be linked with edges from the
42
+ `different_edges` table.
43
+
44
+ 5. The default edge column names have changed. `ancestor_id` is now `parent_id`
45
+ and `descendant_id` is now `child_id`. `ancestor_type` and `descendant_type`
46
+ are now unused.
47
+
48
+ 6. Technically, you no longer need an edge model to use dagnabit. It can,
49
+ however, occasionally be useful. To make an edge model, create a model
50
+ resembling
51
+
52
+ class Edge < ActiveRecord::Base
53
+ extend Dagnabit::Edge::Activation
54
+
55
+ acts_as_edge
56
+ end
@@ -0,0 +1,222 @@
1
+ So I lied
2
+ =========
3
+
4
+ dagnabit is actually still alive. I still think you should be using something
5
+ like Sequel if you really want to do graphs in a SQL database, but if you're
6
+ using ActiveRecord for whatever reason, dagnabit might still be useful.
7
+
8
+ Version 3 is a rework of dagnabit as a PostgreSQL-specific ActiveRecord plugin.
9
+ It's blatantly incompatible with the 2.x series of dagnabit. There are some
10
+ migration notes in MIGRATION.md.
11
+
12
+ dagnabit
13
+ ========
14
+
15
+ dagnabit is (yet another) ActiveRecord plugin for directed acyclic graphs. It
16
+ stores directed acyclic graphs as an adjacency list, using recursive common
17
+ table expressions to perform fast reachability queries.
18
+
19
+ dagnabit was developed at the [Northwestern University Biomedical Informatics
20
+ Center](http://www.nucats.northwestern.edu/centers/nubic/index.html) for a Ruby
21
+ on Rails application designed for management and querying of
22
+ large-and-rapidly-growing biospecimen banks (i.e. for cancer research). The
23
+ application uses directed acyclic graphs for knowledge representation (storage
24
+ and application of workflows) and representing biospecimen heritage.
25
+
26
+ dagnabit is hosted at Gitorious and Github:
27
+
28
+ * <http://gitorious.org/dagnabit/dagnabit>
29
+ * <http://github.com/yipdw/dagnabit>
30
+
31
+ Installation
32
+ ============
33
+
34
+ gem install dagnabit
35
+
36
+ Related work
37
+ ============
38
+
39
+ This plugin was inspired by [Matthew Leventi's acts-as-dag
40
+ plugin](http://github.com/mleventi/acts-as-dag/tree/master). Indeed, Leventi's
41
+ acts-as-dag plugin was originally used in the application from which this plugin
42
+ was extracted.
43
+
44
+ The primary differences between dagnabit and acts-as-dag are:
45
+
46
+ * dagnabit does not maintain a separate transitive closure table, which speeds
47
+ up insertion.
48
+
49
+ * acts-as-dag does not permit linking of unpersisted nodes, i.e.
50
+
51
+ n1 = Vertex.new
52
+ n2 = Vertex.new
53
+ e1 = Edge.new(:parent => n1, :child => n2)
54
+ ... other code ...
55
+
56
+ [n1, n2, e1].map { |o| o.save }
57
+
58
+ With acts-as-dag, one must save the nodes _before_ creating the edge.
59
+ The above code segment works in dagnabit.
60
+
61
+ Database compatibility
62
+ ======================
63
+
64
+ PostgreSQL. That's all I know that'll work with dagnabit, anyway.
65
+
66
+ It's possible other SQL databases will work, but I have no tests to demonstrate
67
+ that situation.
68
+
69
+ Using dagnabit
70
+ ==============
71
+
72
+ Database schema
73
+ ---------------
74
+
75
+ You'll need at least one table for storing your graph's vertices. Additionally,
76
+ for every vertex table you create, you will need one edge table.
77
+
78
+ Polymorphic vertices are supported via single table inheritance.
79
+
80
+ Here's an example schema written as an ActiveRecord schema definition:
81
+
82
+ create_table :vertices do |t|
83
+ t.integer :ordinal
84
+ t.string :type # only if you're using STI
85
+ end
86
+
87
+ create_table :edges do |t|
88
+ t.references :parent, :null => false
89
+ t.references :child, :null => false
90
+ end
91
+
92
+ add_index :edges, [:parent_id, :child_id], :unique => true
93
+
94
+ Maintaining a directed acyclic simple graph and some words on validation
95
+ ------------------------------------------------------------------------
96
+
97
+ dagnabit is designed to operate on directed, acyclic, simple graphs. That means:
98
+
99
+ 1. each edge has a direction (_directed_),
100
+ 2. there cannot exist any edges that create a cycle (_acyclic_),
101
+ 3. any edge must connect two and only two vertices (no _hyperedges_), and
102
+ 4. there may be only zero or one edges connecting any two vertices (_simple_).
103
+
104
+ dagnabit is set up to make the database enforce these invariants:
105
+
106
+ 1. The directionality of each edge is implicit in the edge table structure from
107
+ parent to child.
108
+ 2. dagnabit provides a PL/pgSQL trigger that you can use to abort saving edges
109
+ that, when inserted, causes a cycle. More on this below.
110
+ 3. As each edge may only address one parent and one child, the maximum of two
111
+ vertices is guaranteed. `NOT NULL` constraints on the `parent_id` and
112
+ `child_id` columns guarantee the minimum of two. (It is recommended that you
113
+ also set up foreign key constraints from edges to vertices, though that
114
+ addresses a different issue.)
115
+ 4. The `(parent_id, child_id)` index prevents multiple edges connecting the same
116
+ vertices.
117
+
118
+ You may, of course, relax invariants 2-4 by omitting indices or constraints;
119
+ however, if you do that, you risk problems such as
120
+ {Dagnabit::Vertex::Connectivity} methods generating infinite loops.
121
+
122
+ Note that dagnabit currently does not provide a way to catch data that would
123
+ violate these invariants via ActiveRecord's validation subsystem. Violations,
124
+ therefore, will result in quite nasty (from the ActiveRecord perspective of
125
+ things) `PGError`s.
126
+
127
+ If you design your application code such that these invariants cannot be
128
+ violated via typical user actions, then this is probably fine, because in that
129
+ case the `PGError` exception (which, in this case, you probably don't want to
130
+ handle) indicates either the existence of an error in the code and/or malicious
131
+ activity that was prevented.
132
+
133
+ On the other hand, if users of your application will be building dags as part of
134
+ their interactions with your application, then it is a very real possibility
135
+ that violation of the above invarints may occur in the course of normal user
136
+ activity. In this case, you must trap these errors and provide adequate
137
+ feedback to your users so that they can correct the problem. This, like many UI
138
+ problems, is not a trivial problem to solve, and I do not have any general
139
+ solution for it.
140
+
141
+ dagnabit's cycle-checking trigger
142
+ ---------------------------------
143
+
144
+ dagnabit ships with a PL/pgSQL trigger that can be installed on edge tables.
145
+ The trigger algorithm is run per inserted or updated row, and may be described
146
+ as
147
+
148
+ trigger check_cycle(seen = [], edge = (a, b)):
149
+ if a != b
150
+ if b has no children
151
+ ok
152
+ else
153
+ for each child c of b
154
+ check_cycle(seen + [b], (a, c))
155
+ end
156
+ else
157
+ abort
158
+ end
159
+
160
+ The implementation uses a `WITH RECURSIVE` query.
161
+
162
+ The {Dagnabit::Migration} module provides methods for creating and dropping
163
+ triggers on edge tables:
164
+
165
+ class CreateEdges < ActiveRecord::Migration
166
+ extend Dagnabit::Migration
167
+
168
+ def self.up
169
+ create_table :edges do |t|
170
+ ...
171
+ end
172
+
173
+ create_cycle_check_trigger :edges
174
+ end
175
+
176
+ def self.down
177
+ drop_cycle_check_trigger :edges
178
+
179
+ drop_table :edges
180
+ end
181
+ end
182
+
183
+ Using dagnabit in your application
184
+ ----------------------------------
185
+
186
+ dagnabit is activated on vertex and edge models by extending vertex and edge
187
+ classes with {Dagnabit::Vertex::Activation} and {Dagnabit::Edge::Activation},
188
+ respectively:
189
+
190
+ class Vertex < ActiveRecord::Base
191
+ extend Dagnabit::Vertex::Activation
192
+
193
+ acts_as_vertex
194
+ connected_by 'Edge'
195
+ end
196
+
197
+ class Edge < ActiveRecord::Base
198
+ extend Dagnabit::Edge::Activation
199
+
200
+ acts_as_edge
201
+ connects 'Vertex'
202
+ end
203
+
204
+ By default, the vertex connectivity queries expect the edge table to be called
205
+ "edges", but that is by no means required. Just associate the vertex with a
206
+ different edge model:
207
+
208
+ class OtherVertex < ActiveRecord::Base
209
+ extend Dagnabit::Vertex::Activation
210
+
211
+ acts_as_vertex
212
+ connected_by 'OtherEdge'
213
+ end
214
+
215
+ For further information, see the library API documentation. Also see the
216
+ listing of the `dagnabit-test` program.
217
+
218
+ Copyright
219
+ =========
220
+
221
+ Copyright (c) 2009, 2010 David Yip. Released under the MIT License; see
222
+ LICENSE for details.
@@ -1,46 +1,26 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- #
4
- # Launches an irb session with an in-memory SQLite3 database and Node and Link
5
- # ActiveRecord models.
6
- #
7
- #
8
- require 'rubygems'
9
- require 'bundler'
10
- Bundler.setup
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), %w(.. lib)))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), %w(.. db)))
11
5
 
12
6
  require 'active_record'
13
- require 'sqlite3'
14
7
  require 'irb'
15
8
 
16
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), %w(.. lib)))
17
-
18
9
  require 'dagnabit'
19
10
 
11
+ require 'connection'
20
12
  ActiveRecord::Base.logger = Logger.new("#{File.basename(__FILE__)}.log")
21
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
22
-
23
- ActiveRecord::Schema.define do
24
- [:edges, :edges_transitive_closure_tuples].each do |table|
25
- create_table table do |t|
26
- t.integer :ancestor_id
27
- t.integer :descendant_id
28
- t.string :ancestor_type
29
- t.string :descendant_type
30
- end
31
- end
32
-
33
- create_table :nodes do |t|
34
- t.string :data
35
- end
36
- end
13
+ require 'schema'
14
+
15
+ require 'models/edge'
16
+ require 'models/vertex'
37
17
 
38
- class Edge < ActiveRecord::Base
39
- acts_as_dag_link
18
+ def v(datum)
19
+ Vertex.find_or_create_by_datum(datum)
40
20
  end
41
21
 
42
- class Node < ActiveRecord::Base
43
- acts_as_dag_node_linked_by 'Edge'
22
+ def e(a, b)
23
+ Edge.create(:parent => a, :child => b)
44
24
  end
45
25
 
46
26
  IRB.start