acts_as_oqgraph 0.1.2 → 0.1.3
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 +64 -14
- data/VERSION +1 -1
- data/acts_as_oqgraph.gemspec +1 -1
- data/lib/graph_edge.rb +1 -2
- data/test/test_acts_as_oqgraph.rb +16 -1
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
= ActsAsOQGraph
|
2
2
|
|
3
3
|
This gem can be used with ActiveRecord to access the features of the OQGraph
|
4
4
|
MySQL plugin.
|
@@ -11,7 +11,7 @@ pros and cons of both approaches, It really depends on your needs. Both librarie
|
|
11
11
|
use the C++ Boost graph library at the bottom layers. OQGraph has expensive
|
12
12
|
insert operations but is very fast at delivering the graph data and finding paths in a single SQL query.
|
13
13
|
|
14
|
-
|
14
|
+
== Concepts
|
15
15
|
|
16
16
|
The term graph we are using here is a mathematical one not a pretty picture (sorry designers).
|
17
17
|
For more see: http://en.wikipedia.org/wiki/Graph_(mathematics)
|
@@ -24,7 +24,7 @@ one each way between each node.
|
|
24
24
|
Edges can be assigned positive floating point values called weights. Weights default to 1.0
|
25
25
|
The weights are used in shortest path calculations, a path is shorter if the sum of weights over each edge is smaller.
|
26
26
|
|
27
|
-
|
27
|
+
== What you can do with OQGraph?
|
28
28
|
|
29
29
|
Imagine your shiny new social networking app, FarceBook.
|
30
30
|
You have lots and lots of users each with several friends. How do you find the friends of friends?
|
@@ -43,9 +43,9 @@ 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
45
|
|
46
|
-
It's good for representing tree structures, networks, routes between cities
|
46
|
+
It's good for representing tree structures, networks, routes between cities etc.
|
47
47
|
|
48
|
-
|
48
|
+
== Usage
|
49
49
|
|
50
50
|
In your node model file:
|
51
51
|
|
@@ -53,16 +53,24 @@ In your node model file:
|
|
53
53
|
acts_as_oqgraph
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
=== Options
|
57
|
+
Given in a hash appended to the acts_as_oqgrpah call.
|
57
58
|
|
59
|
+
acts_as_oqgraph :class_name => 'FooLinks',
|
60
|
+
:table_name => "my_foo_links",
|
61
|
+
:oqgraph_table_name => 'foo_oqgraph',
|
62
|
+
:from_key => 'origin',
|
63
|
+
:to_key => 'dest'
|
64
|
+
:weight_column => 'weight'
|
65
|
+
|
58
66
|
* class_name - The name of the edge class, defaults to current class name appended with Edge. eg FooEdge
|
59
67
|
* table_name - The name of the edge table, defaults to table name of the specified class, eg foo_edges
|
60
68
|
* oqgraph_table_name - the name of the volatile oqgraph table. Default foo_edge_oqgraph
|
61
69
|
* from_key - The from key field in the edge table. Default 'from_id'
|
62
70
|
* to_key - The to key field in the edge table. Default: 'to_id'
|
63
|
-
* weight_column - The weight field in the edge table.
|
71
|
+
* weight_column - The weight field in the edge table. Default: weight
|
64
72
|
|
65
|
-
|
73
|
+
== Setup
|
66
74
|
|
67
75
|
This gem requires the use of MySQL or MariaDB with the OQGraph engine plugin.
|
68
76
|
For details of this see: http://openquery.com/products/graph-engine
|
@@ -85,9 +93,9 @@ The associations are:
|
|
85
93
|
edge_model.from
|
86
94
|
|
87
95
|
|
88
|
-
|
96
|
+
= Examples of Use
|
89
97
|
|
90
|
-
Creating edges:
|
98
|
+
=== Creating edges:
|
91
99
|
foo.create_edge_to(bar)
|
92
100
|
foo.create_edge_to_and_from(bar)
|
93
101
|
|
@@ -105,7 +113,7 @@ Removing a edge:
|
|
105
113
|
foo.remove_edge_from(bar)
|
106
114
|
Note that these calls remove ALL edges to bar from foo
|
107
115
|
|
108
|
-
Examining edges:
|
116
|
+
=== Examining edges:
|
109
117
|
|
110
118
|
What nodes point to this one?
|
111
119
|
Gives us an array of nodes that are connected to this one in the inward (from) direction.
|
@@ -119,14 +127,20 @@ Includes the node calling the method.
|
|
119
127
|
bar.reachable
|
120
128
|
foo.reachable?(baz)
|
121
129
|
|
122
|
-
Path Finding:
|
130
|
+
=== Path Finding:
|
123
131
|
foo.shortest_path_to(baz)
|
124
132
|
returns [foo, bar,baz]
|
133
|
+
|
134
|
+
The shortest path to can take a :method which can either be :dijikstra (the default)
|
135
|
+
or :breadth_first. The breadth first method does not take weights into account.
|
136
|
+
It is faster in some cases.
|
137
|
+
|
138
|
+
foo.shortest_path_to(baz, :method => :breadth_first)
|
125
139
|
|
126
140
|
All these methods return the node object with an additional weight field.
|
127
141
|
This enables you to query the weights associated with the edges found.
|
128
142
|
|
129
|
-
|
143
|
+
== Behind the Scenes
|
130
144
|
|
131
145
|
When you declare acts_as_oqgraph then the edge class gets created. You can add extra functionality
|
132
146
|
if you wish to the edge class by the usual Ruby monkey patching methods.
|
@@ -137,6 +151,42 @@ When your application starts up it will put all the edges into the graph table a
|
|
137
151
|
they are created, deleted or modified. This could slow things down at startup but caching classes in production
|
138
152
|
means it does not happen on every request. I will add functionality to only update the oqgraph table if the
|
139
153
|
db server has been stopped, once I figure out the best way to do it.
|
140
|
-
|
154
|
+
|
155
|
+
== How fast is it?
|
156
|
+
I've tested with an application with 10000 nodes and 0 to 9 links from each.
|
157
|
+
|
158
|
+
For a node connected to most of the network finding all reachable nodes averages at about 300ms.
|
159
|
+
This is strongly dependent on how well connected the graph is.
|
160
|
+
|
161
|
+
To find shortest paths between nodes takes about 5 to 10ms per request.
|
162
|
+
Here's an example request:
|
163
|
+
Processing OqgraphUsersController#show_path (for 127.0.0.1 at 2010-07-21 17:09:59) [GET]
|
164
|
+
Parameters: {"id"=>"223", "other_id"=>"2333"}
|
165
|
+
OqgraphUser Load (0.3ms) SELECT * FROM `oqgraph_users` WHERE (`oqgraph_users`.`id` = 223)
|
166
|
+
OqgraphUser Load (0.1ms) SELECT * FROM `oqgraph_users` WHERE (`oqgraph_users`.`id` = 2333)
|
167
|
+
OqgraphUser Load (2.2ms) SELECT oqgraph_users.id,oqgraph_users.first_name,oqgraph_users.last_name, oqgraph_user_oqgraph.weight FROM oqgraph_user_oqgraph JOIN oqgraph_users ON (linkid=id) WHERE latch = 1 AND origid = 223 AND destid = 2333
|
168
|
+
ORDER BY seq;
|
169
|
+
|
170
|
+
Rendering oqgraph_users/show_path
|
171
|
+
Completed in 6ms (View: 2, DB: 3) | 200 OK [http://localhost/oqgraph_users/223/path_to/2333]
|
172
|
+
|
173
|
+
Of course YMMV.
|
174
|
+
|
175
|
+
== Hairy bits, bugs and gotchas
|
176
|
+
|
177
|
+
To keep the oqgraph table up to date the edge model copies all of it records in when first instantiated.
|
178
|
+
This means that if you do not have
|
179
|
+
config.cache_classes = true
|
180
|
+
in your environment and you have a lot of nodes your app will be dog slow. Also the app will be slow to start at first if
|
181
|
+
there are many nodes even when caches are cached.
|
182
|
+
|
183
|
+
I've encountered a bug where the oqgraph table occasionally needs to be dropped and rebuilt. It's being tracked down.
|
184
|
+
If you are not getting any results from the oqgrah table try dropping it and restarting the app.
|
185
|
+
|
186
|
+
I'm working on a way to tell if the oqgraph table is stale. Suggestions would be welcome.
|
187
|
+
|
188
|
+
There is a method 'in_edges' in the edge model class. This does not currently work. 'out_edges' does.
|
189
|
+
|
190
|
+
This gem is very new, the API is liable to change until it stabilises.
|
141
191
|
|
142
192
|
Copyright (c) 2010 Stuart Coyle, released under the MIT license
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/acts_as_oqgraph.gemspec
CHANGED
data/lib/graph_edge.rb
CHANGED
@@ -87,9 +87,8 @@ class GraphEdge < ActiveRecord::Base
|
|
87
87
|
|
88
88
|
def add_to_graph
|
89
89
|
connection.execute <<-EOS
|
90
|
-
|
90
|
+
REPLACE INTO #{oqgraph_table_name} (origid, destid, weight)
|
91
91
|
VALUES (#{self.send(self.class.from_key)}, #{self.send(self.class.to_key)}, #{weight || 1.0})
|
92
|
-
ON DUPLICATE KEY UPDATE origid=#{self.send(self.class.from_key)};
|
93
92
|
EOS
|
94
93
|
end
|
95
94
|
|
@@ -195,8 +195,23 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
|
|
195
195
|
def test_duplicate_links_ignored
|
196
196
|
@test_1.create_edge_to @test_2
|
197
197
|
assert_nothing_raised do
|
198
|
-
@test_1.create_edge_to @test_2
|
198
|
+
@test_1.create_edge_to @test_2
|
199
199
|
end
|
200
200
|
end
|
201
|
+
|
202
|
+
def test_duplicate_link_error
|
203
|
+
ActiveRecord::Base.connection.execute("INSERT INTO test_model_oqgraph (destid, origid, weight) VALUES (99,99,1.0);")
|
204
|
+
assert_raises ActiveRecord::StatementInvalid do
|
205
|
+
ActiveRecord::Base.connection.execute("INSERT INTO test_model_oqgraph (destid, origid, weight) VALUES (99,99,1.0);")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_duplicate_link_error_fix
|
210
|
+
ActiveRecord::Base.connection.execute("REPLACE INTO test_model_oqgraph (destid, origid, weight) VALUES (99,99,1.0);")
|
211
|
+
assert_nothing_raised do
|
212
|
+
ActiveRecord::Base.connection.execute("REPLACE INTO test_model_oqgraph (destid, origid, weight) VALUES (99,99,1.0);")
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
201
216
|
|
202
217
|
end
|