acts_as_oqgraph 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.1.6
@@ -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.5"
8
+ s.version = "0.1.6"
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"]
@@ -60,7 +60,10 @@ module OQGraph
60
60
  # Path Finding:
61
61
  # foo.shortest_path_to(baz)
62
62
  # returns [foo, bar,baz]
63
- #
63
+ #
64
+ # With breadth first edge weights are not taken into account:
65
+ # foo.shortest_path_to(baz, :method => :breadth_first)
66
+ #
64
67
  # All these methods return the node object with an additional weight field.
65
68
  # This enables you to query the weights associated with the edges found.
66
69
  #
@@ -139,9 +142,7 @@ module OQGraph
139
142
  end
140
143
  return result
141
144
  rescue ActiveRecord::StatementInvalid => e
142
- # This will occur if the table is not using MySQL
143
- # TODO: Raise a sensible error message here.
144
- return false
145
+ raise "MySQL or MariaDB 5.1 or above with the OQGRAPH engine is required for the acts_as_oqgraph gem.\nThe following error was raised: #{e.inspect}"
145
146
  end
146
147
  end
147
148
  end
@@ -178,7 +179,7 @@ module OQGraph
178
179
 
179
180
  # Returns an array of all nodes which can trace to this node
180
181
  def originating
181
- edge_class.originating_vertices(self)
182
+ edge_class.originating_nodes(self)
182
183
  end
183
184
 
184
185
  # true if the other node can reach this node.
@@ -188,7 +189,7 @@ module OQGraph
188
189
 
189
190
  # Returns all nodes reachable from this node.
190
191
  def reachable
191
- edge_class.reachable_vertices(self)
192
+ edge_class.reachable_nodes(self)
192
193
  end
193
194
 
194
195
  # true if the other node is reachable from this one
data/lib/graph_edge.rb CHANGED
@@ -6,12 +6,13 @@ class GraphEdge < ActiveRecord::Base
6
6
  after_destroy :remove_from_graph
7
7
  after_update :update_graph
8
8
 
9
-
10
9
  # Creates the OQgraph table if it does not exist.
11
10
  # Deletes all entries if it does exist and then repopulates with
12
- # current edges. TODO Optimise this so that it only does so if the
13
- # DB server has been restarted.
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.
14
13
  def self.create_graph_table
14
+ connection.execute "DROP TABLE IF EXISTS #{oqgraph_table_name}"
15
+
15
16
  connection.execute <<-EOS
16
17
  CREATE TABLE IF NOT EXISTS #{oqgraph_table_name} (
17
18
  latch SMALLINT UNSIGNED NULL,
@@ -25,13 +26,17 @@ class GraphEdge < ActiveRecord::Base
25
26
  ) ENGINE=OQGRAPH;
26
27
  EOS
27
28
 
28
- connection.execute("DELETE FROM #{oqgraph_table_name}")
29
-
30
- self.all.each do |edge|
31
- edge.add_to_graph
32
- end
29
+ connection.execute <<-EOS
30
+ INSERT INTO #{oqgraph_table_name} (origid, destid, weight)
31
+ SELECT #{from_key}, #{to_key}, #{weight_column} FROM #{table_name}
32
+ EOS
33
33
  end
34
34
 
35
+ # Returns the shortest path from node to node.
36
+ # +options+ A hash containing options.
37
+ # The only option is :method which can be
38
+ # :breadth_first to do a breadth first search.
39
+ # It defaults to using Djikstra's algorithm.
35
40
  def self.shortest_path(from, to, options)
36
41
  latch = case options[:method]
37
42
  when :breadth_first then 2
@@ -48,73 +53,85 @@ class GraphEdge < ActiveRecord::Base
48
53
  node_class.find_by_sql select_for_node << sql
49
54
  end
50
55
 
51
- def self.originating_vertices(to)
56
+ # Finds all the nodes that lead to the node
57
+ def self.originating_nodes(node)
52
58
  sql = <<-EOS
53
- WHERE latch = 1 AND destid = #{to.id}
59
+ WHERE latch = 1 AND destid = #{node.id}
54
60
  ORDER BY seq;
55
61
  EOS
56
62
 
57
63
  node_class.find_by_sql select_for_node << sql
58
64
  end
59
65
 
60
- def self.reachable_vertices(from)
66
+ # Finds all the nodes that are reachable from the node
67
+ def self.reachable_nodes(node)
61
68
  sql = <<-EOS
62
- WHERE latch = 1 AND origid = #{from.id}
69
+ WHERE latch = 1 AND origid = #{node.id}
63
70
  ORDER BY seq;
64
71
  EOS
65
72
 
66
73
  node_class.find_by_sql select_for_node << sql
67
74
  end
68
75
 
76
+ # Finds the edges leading directly into the node
69
77
  # FIXME: Note this currently does not work.
70
78
  # I suspect a bug in OQGRaph engine.
71
- def self.in_edges(to)
79
+ # Using the node classes incoming_nodes is equivalent to this.
80
+ def self.in_edges(node)
72
81
  sql = <<-EOS
73
- WHERE latch = 0 AND destid = #{to.id}
82
+ WHERE latch = 0 AND destid = #{node.id}
74
83
  EOS
75
84
 
76
85
  node_class.find_by_sql select_for_node << sql
77
86
  end
78
87
 
79
- def self.out_edges(from)
88
+ # Finds the edges leading directly out of the node
89
+ def self.out_edges(node)
80
90
  sql = <<-EOS
81
- WHERE latch = 0 AND origid = #{from.id}
91
+ WHERE latch = 0 AND origid = #{node.id}
82
92
  EOS
83
93
 
84
94
  node_class.find_by_sql select_for_node << sql
85
95
  end
86
-
87
- def add_to_graph
88
- connection.execute <<-EOS
89
- REPLACE INTO #{oqgraph_table_name} (origid, destid, weight)
90
- VALUES (#{self.send(self.class.from_key)}, #{self.send(self.class.to_key)}, #{weight || 1.0})
91
- EOS
92
- end
93
96
 
94
97
  private
95
-
96
- def remove_from_graph
97
- # Ignores trying to delete nonexistent records
98
- connection.execute <<-EOS
99
- DELETE IGNORE FROM #{oqgraph_table_name} WHERE origid = #{self.send(self.class.from_key)} AND destid = #{self.send(self.class.to_key)};
100
- EOS
101
- end
102
-
103
- def update_graph
104
- connection.execute <<-EOS
105
- UPDATE #{oqgraph_table_name}
106
- SET origid = #{self.send(self.class.from_key)},
107
- destid = #{self.send(self.class.to_key)},
108
- weight = #{weight}
109
- WHERE origid = #{self.send(self.class.from_key + '_was')} AND destid = #{self.send(self.class.to_key + '_was')};
110
- EOS
111
- end
98
+
99
+ # Callback to add new graph edges to the OQGraph table
100
+ def add_to_graph
101
+ connection.execute <<-EOS
102
+ REPLACE INTO #{oqgraph_table_name} (origid, destid, weight)
103
+ VALUES (#{self.send(self.class.from_key)}, #{self.send(self.class.to_key)}, #{self.send(self.class.weight_column) || 1.0})
104
+ EOS
105
+ end
106
+
107
+
108
+ # Callback to remove deleted graph edges from the OQGraph table
109
+ def remove_from_graph
110
+ # Ignores trying to delete nonexistent records
111
+ connection.execute <<-EOS
112
+ DELETE IGNORE FROM #{oqgraph_table_name} WHERE origid = #{self.send(self.class.from_key)} AND destid = #{self.send(self.class.to_key)};
113
+ EOS
114
+ end
115
+
116
+ # Callback to update graph edges in the OQGraph table
117
+ def update_graph
118
+ connection.execute <<-EOS
119
+ UPDATE #{oqgraph_table_name}
120
+ SET origid = #{self.send(self.class.from_key)},
121
+ destid = #{self.send(self.class.to_key)},
122
+ weight = #{self.send(self.class.weight_column)}
123
+ WHERE origid = #{self.send(self.class.from_key + '_was')} AND destid = #{self.send(self.class.to_key + '_was')};
124
+ EOS
125
+ end
126
+
127
+ # nodoc
112
128
  def self.select_for_node
113
129
  sql = "SELECT "
114
130
  sql << node_class.columns.map{|column| "#{node_table}.#{column.name}"}.join(",")
115
131
  sql << ", #{oqgraph_table_name}.weight FROM #{oqgraph_table_name} JOIN #{node_table} ON (linkid=id) "
116
132
  end
117
133
 
134
+ # Returns the table containing the nodes for these edges.
118
135
  def self.node_table
119
136
  node_class.table_name
120
137
  end
@@ -2,7 +2,6 @@ require 'helper'
2
2
 
3
3
  class TestActsAsOqgraph < ActiveSupport::TestCase
4
4
  def setup
5
-
6
5
  ActiveRecord::Base.establish_connection(
7
6
  :adapter => "mysql",
8
7
  :host => "localhost",
@@ -13,8 +12,9 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
13
12
 
14
13
  ActiveRecord::Base.connection.execute("CREATE TABLE IF NOT EXISTS test_models(id INTEGER DEFAULT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) );")
15
14
  ActiveRecord::Base.connection.execute("CREATE TABLE IF NOT EXISTS test_model_edges(id INTEGER DEFAULT NULL AUTO_INCREMENT PRIMARY KEY, from_id INTEGER, to_id INTEGER, weight DOUBLE);")
16
- ActiveRecord::Base.connection.execute("CREATE TABLE IF NOT EXISTS custom_edges(id INTEGER DEFAULT NULL AUTO_INCREMENT PRIMARY KEY, from_id INTEGER, to_id INTEGER, weight DOUBLE);")
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);")
17
16
 
17
+ # These requires need the tables to be created.
18
18
  require File.join(File.dirname(__FILE__),'models/custom_test_model')
19
19
  require File.join(File.dirname(__FILE__),'models/test_model')
20
20
 
@@ -24,7 +24,6 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
24
24
  @test_4 = TestModel.create(:name => 'd')
25
25
  @test_5 = TestModel.create(:name => 'e')
26
26
  @test_6 = TestModel.create(:name => 'f')
27
-
28
27
  end
29
28
 
30
29
  def teardown
@@ -33,7 +32,6 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
33
32
  ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS custom_edges;")
34
33
  ActiveRecord::Base.connection.execute("DELETE FROM test_model_oqgraph;")
35
34
  ActiveRecord::Base.connection.execute("DELETE FROM custom_test_model_oqgraph;")
36
-
37
35
  end
38
36
 
39
37
  def test_edge_table_and_class_names
@@ -70,6 +68,11 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
70
68
  assert_equal 'dest_id', CustomEdge.to_key
71
69
  assert_equal 'orig_id', CustomEdge.from_key
72
70
  assert_equal 'length', CustomEdge.weight_column
71
+ mysql = Mysql.new('localhost', 'root', '', 'test')
72
+ fields = mysql.list_fields('custom_edges').fetch_fields.map{|f| f.name}
73
+ assert fields.include?('orig_id')
74
+ assert fields.include?('dest_id')
75
+ assert fields.include?('length')
73
76
  end
74
77
 
75
78
  def test_test_model_edge_creation
@@ -222,7 +225,7 @@ class TestActsAsOqgraph < ActiveSupport::TestCase
222
225
  def test_deletion_of_nonexistent_edge_raises_error
223
226
  edge = @test_1.create_edge_to @test_2
224
227
  ActiveRecord::Base.connection.execute("DELETE FROM test_model_oqgraph WHERE destid = #{edge.to_id} AND origid = #{edge.from_id}")
225
- assert_raises ActiveRecord::StatementInvalid do
228
+ assert_nothing_raised do
226
229
  edge.destroy
227
230
  end
228
231
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 5
9
- version: 0.1.5
8
+ - 6
9
+ version: 0.1.6
10
10
  platform: ruby
11
11
  authors:
12
12
  - Stuart Coyle