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