dagnabit 2.2.1 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -12
- data/VERSION.yml +1 -1
- data/bin/dagnabit-test +1 -1
- data/dagnabit.gemspec +5 -3
- data/lib/dagnabit/link/transitive_closure_recalculation/on_update.rb +4 -0
- data/test/benchmark/helper.rb +43 -0
- data/test/benchmark/linear_benchmark.rb +41 -0
- data/test/dagnabit/link/test_transitive_closure_recalculation.rb +11 -0
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -1,9 +1,3 @@
|
|
1
|
-
= This is the GitHub branch
|
2
|
-
|
3
|
-
This branch is updated irregularly. The canonical dagnabit repository is hosted on Gitorious:
|
4
|
-
|
5
|
-
http://gitorious.org/dagnabit/dagnabit
|
6
|
-
|
7
1
|
= dagnabit
|
8
2
|
|
9
3
|
dagnabit is (yet another) ActiveRecord plugin for directed acyclic graphs. It
|
@@ -83,6 +77,9 @@ In your node models, put
|
|
83
77
|
+acts_as_dag_link+ accepts some configuration options. These configuration
|
84
78
|
options are discussed below.
|
85
79
|
|
80
|
+
For a code example of a complete setup, see the <tt>dagnabit-test</tt> script
|
81
|
+
in +bin+.
|
82
|
+
|
86
83
|
=== Link table schemas
|
87
84
|
|
88
85
|
Link storage is accomplished with two tables having identical schemas. One
|
@@ -109,6 +106,9 @@ The behavior of the plugin with a deviant schema is unspecified.
|
|
109
106
|
* +transitive_closure_table_name+: The table where tuples in the transitive
|
110
107
|
closure of the graph should be stored. Defaults to
|
111
108
|
<tt>#{link_table_name}_transitive_closure_tuples</tt>.
|
109
|
+
* +transitive_closure_class_name+: The transitive closure is represented with
|
110
|
+
an ActiveRecord class whose name defaults to +TransitiveClosureLink+. Set this
|
111
|
+
option to change that name.
|
112
112
|
* +descendant_id_column+: Column in the link tables for recording the ID of a
|
113
113
|
descendant. Defaults to +descendant_id+.
|
114
114
|
* +ancestor_id_column+: Column in the link tables for recording the ID of a
|
@@ -146,22 +146,20 @@ edge model was called +Link+, these would be valid expressions:
|
|
146
146
|
|
147
147
|
+acts_as_dag_node+ adds the following instance methods to nodes:
|
148
148
|
|
149
|
-
* +
|
149
|
+
* +children+: All children of the node. Read-only.
|
150
|
+
* +parents+: All parents of the node. Read-only.
|
150
151
|
* +ancestors+: All ancestors of the node. Read-only.
|
152
|
+
* +descendants+: All descendants of the node. Read-only.
|
151
153
|
|
152
154
|
=== Link interface
|
153
155
|
|
154
156
|
The following class methods are available on links:
|
155
157
|
|
156
158
|
* +paths+: Retrieves all paths from one node to another.
|
157
|
-
*
|
158
|
-
* +find_edge+: Finds an edge from one node to another, or nil if none exists.
|
159
|
+
* <tt>path?</tt>: Returns whether a node is reachable from another node.
|
159
160
|
* +build_edge+: Builds an edge from one node to another node.
|
160
161
|
* +connect+: Builds and saves an edge from one node to another. Identical to
|
161
162
|
<tt>build_edge(nodeA, nodeB).save</tt>.
|
162
|
-
* <tt>connect!</tt>: Builds and saves an edge from one node to another, raising
|
163
|
-
an exception if save fails. Identical to <tt>build_edge(nodeA,
|
164
|
-
nodeB).save!</tt>.
|
165
163
|
|
166
164
|
== Introduction to the codebase
|
167
165
|
|
data/VERSION.yml
CHANGED
data/bin/dagnabit-test
CHANGED
data/dagnabit.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dagnabit}
|
8
|
-
s.version = "2.2.
|
8
|
+
s.version = "2.2.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["David Yip"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2010-02-04}
|
13
13
|
s.default_executable = %q{dagnabit-test}
|
14
14
|
s.email = %q{yipdw@northwestern.edu}
|
15
15
|
s.executables = ["dagnabit-test"]
|
@@ -75,7 +75,9 @@ Gem::Specification.new do |s|
|
|
75
75
|
s.rubygems_version = %q{1.3.5}
|
76
76
|
s.summary = %q{Directed acyclic graph plugin for ActiveRecord}
|
77
77
|
s.test_files = [
|
78
|
-
"test/
|
78
|
+
"test/benchmark/helper.rb",
|
79
|
+
"test/benchmark/linear_benchmark.rb",
|
80
|
+
"test/connections/native_postgresql/connection.rb",
|
79
81
|
"test/connections/native_sqlite3/connection.rb",
|
80
82
|
"test/dagnabit/link/test_associations.rb",
|
81
83
|
"test/dagnabit/link/test_class_methods.rb",
|
@@ -3,7 +3,11 @@ module Dagnabit
|
|
3
3
|
module TransitiveClosureRecalculation
|
4
4
|
module OnUpdate
|
5
5
|
def after_update
|
6
|
+
current_values = dag_link_column_names.map { |n| connection.quote(send(n)) }
|
6
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
|
+
|
7
11
|
update_transitive_closure_for_destroy(*old_values)
|
8
12
|
update_transitive_closure_for_create
|
9
13
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_support/test_case'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
require 'dagnabit'
|
9
|
+
|
10
|
+
ActiveRecord::Base.logger = Logger.new("benchmark.log")
|
11
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define do
|
14
|
+
[:links, :links_transitive_closure_tuples].each do |table|
|
15
|
+
create_table table do |t|
|
16
|
+
t.integer :ancestor_id
|
17
|
+
t.integer :descendant_id
|
18
|
+
t.string :ancestor_type
|
19
|
+
t.string :descendant_type
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table :nodes do |t|
|
24
|
+
t.string :data
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Link < ActiveRecord::Base
|
29
|
+
acts_as_dag_link
|
30
|
+
end
|
31
|
+
|
32
|
+
class Node < ActiveRecord::Base
|
33
|
+
acts_as_dag_node_linked_by 'Link'
|
34
|
+
end
|
35
|
+
|
36
|
+
REPETITIONS = 5
|
37
|
+
|
38
|
+
class BenchmarkTestCase < ActiveSupport::TestCase
|
39
|
+
def teardown
|
40
|
+
Node.delete_all
|
41
|
+
Link.delete_all
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class LinearBenchmark < BenchmarkTestCase
|
4
|
+
def test_speed
|
5
|
+
Benchmark.bm do |x|
|
6
|
+
root = Node.create
|
7
|
+
last_node = root
|
8
|
+
|
9
|
+
100.times do |i|
|
10
|
+
target = Node.create
|
11
|
+
|
12
|
+
x.report("Graph depth: #{i+1}") { Link.connect(last_node, target) }
|
13
|
+
|
14
|
+
assert_equal 1, last_node.children.length
|
15
|
+
assert_equal i+1, root.descendants.length
|
16
|
+
|
17
|
+
last_node = target
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_deletion_from_long_list
|
23
|
+
nodes = []
|
24
|
+
last_node = Node.create
|
25
|
+
|
26
|
+
puts 'Building long list...'
|
27
|
+
50.times do |i|
|
28
|
+
target = Node.create
|
29
|
+
Link.connect(last_node, target)
|
30
|
+
last_node = target
|
31
|
+
|
32
|
+
nodes << target
|
33
|
+
end
|
34
|
+
|
35
|
+
Benchmark.bm do |x|
|
36
|
+
nodes.each_with_index do |node, i|
|
37
|
+
x.report("Destroying node #{i}") { node.destroy }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -134,6 +134,17 @@ module Dagnabit
|
|
134
134
|
tc = CustomTransitiveClosureLink.find(:first, :conditions => { :the_ancestor_id => n1.id, :the_descendant_id => n3.id })
|
135
135
|
assert_nil tc, 'expected to not find path from n1 to n3'
|
136
136
|
end
|
137
|
+
|
138
|
+
should "not recalculate transitive closure if neither a link's source nor target changed" do
|
139
|
+
n1 = ::Node.create
|
140
|
+
n2 = ::Node.create
|
141
|
+
|
142
|
+
l1 = ::Link.new(:ancestor => n1, :descendant => n2)
|
143
|
+
|
144
|
+
l1.expects(:update_transitive_closure_for_destroy).never
|
145
|
+
l1.save
|
146
|
+
l1.save
|
147
|
+
end
|
137
148
|
end
|
138
149
|
end
|
139
150
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dagnabit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Yip
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-02-04 00:00:00 -06:00
|
13
13
|
default_executable: dagnabit-test
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -141,6 +141,8 @@ signing_key:
|
|
141
141
|
specification_version: 3
|
142
142
|
summary: Directed acyclic graph plugin for ActiveRecord
|
143
143
|
test_files:
|
144
|
+
- test/benchmark/helper.rb
|
145
|
+
- test/benchmark/linear_benchmark.rb
|
144
146
|
- test/connections/native_postgresql/connection.rb
|
145
147
|
- test/connections/native_sqlite3/connection.rb
|
146
148
|
- test/dagnabit/link/test_associations.rb
|