acts_as_oqgraph 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- *ActsAsOQGraph*
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
- *Concepts*
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
- *What you can do with OQGraph?*
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, roads etc.
46
+ It's good for representing tree structures, networks, routes between cities etc.
47
47
 
48
- *Usage*
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
- *Options*
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
- *Setup*
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
- *Examples of use*
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
- *Behind the Scenes*
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.2
1
+ 0.1.3
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{acts_as_oqgraph}
8
- s.version = "0.1.2"
8
+ s.version = "0.1.3"
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"]
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
- INSERT INTO #{oqgraph_table_name} (origid, destid, weight)
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
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 2
9
- version: 0.1.2
8
+ - 3
9
+ version: 0.1.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - Stuart Coyle