acts_as_oqgraph 0.1.6 → 0.1.7
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.
- data/README.rdoc +8 -7
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/acts_as_oqgraph.gemspec +5 -5
- data/lib/acts_as_oqgraph.rb +8 -1
- data/lib/graph_edge.rb +14 -12
- data/test/test_acts_as_oqgraph.rb +19 -4
- metadata +7 -5
data/README.rdoc
CHANGED
@@ -42,6 +42,7 @@ Then I can call:
|
|
42
42
|
and I get the whole tree of friends of friends etc...
|
43
43
|
|
44
44
|
Imagine you have a maze to solve. With OQGraph the solution is as simple as: start_cell.shortest_path_to(finish_cell).
|
45
|
+
See the demo code at http://github.com/stuart/acts_as_oqgraph_demo
|
45
46
|
|
46
47
|
It's good for representing tree structures, networks, routes between cities etc.
|
47
48
|
|
@@ -149,8 +150,9 @@ The OQGraph table will also get created if it does not exist. The OQGraph table
|
|
149
150
|
memory only. The table structure is not volatile and gets stored in the db.
|
150
151
|
When your application starts up it will put all the edges into the graph table and update them as
|
151
152
|
they are created, deleted or modified. This could slow things down at startup but caching classes in production
|
152
|
-
means it does not happen on every request.
|
153
|
-
|
153
|
+
means it does not happen on every request. The graph table is only rewritten now when the DB has been restarted.
|
154
|
+
You can use this code to force the graph to be rebuilt:
|
155
|
+
NodeModel.rebuild_graph
|
154
156
|
|
155
157
|
== How fast is it?
|
156
158
|
I've tested with an application with 10000 nodes and 0 to 9 links from each.
|
@@ -175,15 +177,14 @@ Of course YMMV.
|
|
175
177
|
== Hairy bits, bugs and gotchas
|
176
178
|
|
177
179
|
To keep the oqgraph table up to date the edge model copies all of it records in when first instantiated.
|
178
|
-
This means that
|
179
|
-
|
180
|
-
|
181
|
-
there are many nodes even when caches are cached.
|
180
|
+
This means that on first startup the app can be slow to respond until the whole graph has been written.
|
181
|
+
This should not need to happen again unless the DB is restarted. You can get the MySQL server to update the graph
|
182
|
+
by using the --init-file=<SQLfile> option in my.cnf with the appropriate SQL in it.
|
182
183
|
|
183
184
|
I've encountered a bug where the oqgraph table occasionally needs to be dropped and rebuilt. It's being tracked down.
|
184
185
|
If you are not getting any results from the oqgrah table try dropping it and restarting the app.
|
185
186
|
|
186
|
-
I'm working on a way to tell if the oqgraph table is stale. Suggestions would be welcome.
|
187
|
+
I'm working on a way to tell if the oqgraph table is stale other than by the current count of rows. Suggestions would be welcome.
|
187
188
|
|
188
189
|
There is a method 'in_edges' in the edge model class. This does not currently work. 'out_edges' does.
|
189
190
|
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "acts_as_oqgraph"
|
8
8
|
gem.summary = %Q{Use the Open Query Graph engine with Active Record}
|
9
|
-
gem.description = %Q{Acts As OQGraph allows ActiveRecord models to use the fast
|
9
|
+
gem.description = %Q{Acts As OQGraph allows ActiveRecord models to use the fast and powerful OQGraph engine for MYSQL.}
|
10
10
|
gem.email = "stuart.coyle@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/stuart/acts_as_oqgraph"
|
12
12
|
gem.authors = ["Stuart Coyle"]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.7
|
data/acts_as_oqgraph.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{acts_as_oqgraph}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.7"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Stuart Coyle"]
|
12
|
-
s.date = %q{
|
13
|
-
s.description = %q{Acts As OQGraph allows ActiveRecord models to use the fast
|
12
|
+
s.date = %q{2011-06-06}
|
13
|
+
s.description = %q{Acts As OQGraph allows ActiveRecord models to use the fast and powerful OQGraph engine for MYSQL.}
|
14
14
|
s.email = %q{stuart.coyle@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE",
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |s|
|
|
34
34
|
s.homepage = %q{http://github.com/stuart/acts_as_oqgraph}
|
35
35
|
s.rdoc_options = ["--charset=UTF-8"]
|
36
36
|
s.require_paths = ["lib"]
|
37
|
-
s.rubygems_version = %q{1.3.
|
37
|
+
s.rubygems_version = %q{1.3.7}
|
38
38
|
s.summary = %q{Use the Open Query Graph engine with Active Record}
|
39
39
|
s.test_files = [
|
40
40
|
"test/helper.rb",
|
@@ -47,7 +47,7 @@ Gem::Specification.new do |s|
|
|
47
47
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
48
|
s.specification_version = 3
|
49
49
|
|
50
|
-
if Gem::Version.new(Gem::
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
51
|
else
|
52
52
|
end
|
53
53
|
else
|
data/lib/acts_as_oqgraph.rb
CHANGED
@@ -36,7 +36,9 @@ module OQGraph
|
|
36
36
|
# The field names and table name can be changed via the options listed above.
|
37
37
|
#
|
38
38
|
# The gem will automatically create the oqgraph table.
|
39
|
-
#
|
39
|
+
# To rebuild the oqgraph table do:
|
40
|
+
# Model.rebuild_graph
|
41
|
+
#
|
40
42
|
# Examples of use:
|
41
43
|
#
|
42
44
|
# Creating and removing edges:
|
@@ -127,9 +129,14 @@ module OQGraph
|
|
127
129
|
'#{edge_table_name}'
|
128
130
|
end
|
129
131
|
|
132
|
+
def self.rebuild_graph
|
133
|
+
edge_class.create_graph_table
|
134
|
+
end
|
130
135
|
EOF
|
131
136
|
end
|
132
137
|
|
138
|
+
|
139
|
+
|
133
140
|
private
|
134
141
|
|
135
142
|
# Check that we have the OQGraph engine plugin installed in MySQL
|
data/lib/graph_edge.rb
CHANGED
@@ -6,13 +6,8 @@ class GraphEdge < ActiveRecord::Base
|
|
6
6
|
after_destroy :remove_from_graph
|
7
7
|
after_update :update_graph
|
8
8
|
|
9
|
-
# Creates the OQgraph table if it does not exist.
|
10
|
-
|
11
|
-
# current edges. TODO: Optimise this so that it only does so if the
|
12
|
-
# DB server has been restarted rather than when the app is restarted.
|
13
|
-
def self.create_graph_table
|
14
|
-
connection.execute "DROP TABLE IF EXISTS #{oqgraph_table_name}"
|
15
|
-
|
9
|
+
# Creates the OQgraph table if it does not exist and populate with edges.
|
10
|
+
def self.create_graph_table
|
16
11
|
connection.execute <<-EOS
|
17
12
|
CREATE TABLE IF NOT EXISTS #{oqgraph_table_name} (
|
18
13
|
latch SMALLINT UNSIGNED NULL,
|
@@ -24,12 +19,15 @@ class GraphEdge < ActiveRecord::Base
|
|
24
19
|
KEY (latch, origid, destid) USING HASH,
|
25
20
|
KEY (latch, destid, origid) USING HASH
|
26
21
|
) ENGINE=OQGRAPH;
|
27
|
-
|
22
|
+
EOS
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
# if the DB server has restarted then there will be no records in the oqgraph table.
|
25
|
+
if !up_to_date?
|
26
|
+
connection.execute <<-EOS
|
27
|
+
REPLACE INTO #{oqgraph_table_name} (origid, destid, weight)
|
28
|
+
SELECT #{from_key}, #{to_key}, #{weight_column} FROM #{table_name}
|
32
29
|
EOS
|
30
|
+
end
|
33
31
|
end
|
34
32
|
|
35
33
|
# Returns the shortest path from node to node.
|
@@ -75,7 +73,7 @@ class GraphEdge < ActiveRecord::Base
|
|
75
73
|
|
76
74
|
# Finds the edges leading directly into the node
|
77
75
|
# FIXME: Note this currently does not work.
|
78
|
-
# I suspect a bug in
|
76
|
+
# I suspect a bug in OQGraph engine.
|
79
77
|
# Using the node classes incoming_nodes is equivalent to this.
|
80
78
|
def self.in_edges(node)
|
81
79
|
sql = <<-EOS
|
@@ -135,4 +133,8 @@ private
|
|
135
133
|
def self.node_table
|
136
134
|
node_class.table_name
|
137
135
|
end
|
136
|
+
|
137
|
+
def self.up_to_date?
|
138
|
+
connection.select_value("SELECT COUNT(*) FROM #{oqgraph_table_name}") == connection.select_value("SELECT COUNT(*) FROM #{table_name}")
|
139
|
+
end
|
138
140
|
end
|
@@ -15,8 +15,8 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
|
|
15
15
|
ActiveRecord::Base.connection.execute("CREATE TABLE IF NOT EXISTS custom_edges(id INTEGER DEFAULT NULL AUTO_INCREMENT PRIMARY KEY, orig_id INTEGER, dest_id INTEGER, length DOUBLE);")
|
16
16
|
|
17
17
|
# These requires need the tables to be created.
|
18
|
-
require File.join(File.dirname(__FILE__),
|
19
|
-
require File.join(File.dirname(__FILE__),
|
18
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),"models/custom_test_model")
|
19
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),"models/test_model")
|
20
20
|
|
21
21
|
@test_1 = TestModel.create(:name => 'a')
|
22
22
|
@test_2 = TestModel.create(:name => 'b')
|
@@ -134,7 +134,7 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def test_getting_shortest_path_more_complex
|
137
|
-
#
|
137
|
+
#
|
138
138
|
# a -- b -- c -- d
|
139
139
|
# | /
|
140
140
|
# e-- f
|
@@ -222,12 +222,27 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
|
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
225
|
+
# There's an odd case here where MySQL would raise an error only when using Rails.
|
225
226
|
def test_deletion_of_nonexistent_edge_raises_error
|
226
227
|
edge = @test_1.create_edge_to @test_2
|
227
228
|
ActiveRecord::Base.connection.execute("DELETE FROM test_model_oqgraph WHERE destid = #{edge.to_id} AND origid = #{edge.from_id}")
|
228
229
|
assert_nothing_raised do
|
229
230
|
edge.destroy
|
230
231
|
end
|
231
|
-
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_rebuild_graph
|
235
|
+
@test_1.create_edge_to @test_2
|
236
|
+
@test_2.create_edge_to @test_3
|
237
|
+
@test_3.create_edge_to @test_4
|
238
|
+
# Simulate the DB restart
|
239
|
+
ActiveRecord::Base.connection.execute("DELETE FROM test_model_oqgraph;")
|
240
|
+
TestModel.rebuild_graph
|
232
241
|
|
242
|
+
assert_equal [@test_1, @test_2, @test_3, @test_4], @test_1.shortest_path_to(@test_4)
|
243
|
+
assert_equal ['a','b','c','d'], @test_1.shortest_path_to(@test_4).map(&:name)
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
|
233
248
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 7
|
9
|
+
version: 0.1.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Stuart Coyle
|
@@ -14,11 +14,11 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2011-06-06 00:00:00 +10:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
21
|
-
description: Acts As OQGraph allows ActiveRecord models to use the fast
|
21
|
+
description: Acts As OQGraph allows ActiveRecord models to use the fast and powerful OQGraph engine for MYSQL.
|
22
22
|
email: stuart.coyle@gmail.com
|
23
23
|
executables: []
|
24
24
|
|
@@ -51,6 +51,7 @@ rdoc_options:
|
|
51
51
|
require_paths:
|
52
52
|
- lib
|
53
53
|
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
54
55
|
requirements:
|
55
56
|
- - ">="
|
56
57
|
- !ruby/object:Gem::Version
|
@@ -58,6 +59,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
59
|
- 0
|
59
60
|
version: "0"
|
60
61
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
61
63
|
requirements:
|
62
64
|
- - ">="
|
63
65
|
- !ruby/object:Gem::Version
|
@@ -67,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
69
|
requirements: []
|
68
70
|
|
69
71
|
rubyforge_project:
|
70
|
-
rubygems_version: 1.3.
|
72
|
+
rubygems_version: 1.3.7
|
71
73
|
signing_key:
|
72
74
|
specification_version: 3
|
73
75
|
summary: Use the Open Query Graph engine with Active Record
|