acts_as_oqgraph 0.1.5 → 0.1.6

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/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