acts-as-dag 2.0.1 → 2.5.0
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 +9 -10
- data/lib/dag.rb +18 -0
- data/lib/dag/columns.rb +26 -0
- data/lib/dag/dag.rb +296 -0
- data/lib/dag/edges.rb +301 -0
- data/lib/dag/poly_columns.rb +12 -0
- data/lib/dag/polymorphic.rb +76 -0
- data/lib/dag/standard.rb +62 -0
- data/lib/dag/validators.rb +58 -0
- data/test/dag_test.rb +0 -1
- data/test/database.test +0 -0
- metadata +12 -4
- data/lib/active_record/acts/dag.rb +0 -803
@@ -0,0 +1,12 @@
|
|
1
|
+
module Dag
|
2
|
+
#Methods that show the columns for polymorphic DAGs
|
3
|
+
module PolyColumns
|
4
|
+
def ancestor_type_column_name
|
5
|
+
acts_as_dag_options[:ancestor_type_column]
|
6
|
+
end
|
7
|
+
|
8
|
+
def descendant_type_column_name
|
9
|
+
acts_as_dag_options[:descendant_type_column]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Dag
|
2
|
+
module Polymorphic
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :include, PolyEdgeInstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
#Contains nested classes in the link model for polymorphic DAGs
|
9
|
+
#Encapsulates the necessary information about a graph node
|
10
|
+
class EndPoint
|
11
|
+
#Does the endpoint match a model or another endpoint
|
12
|
+
def matches?(other)
|
13
|
+
return (self.id == other.id) && (self.type == other.type) if other.is_a?(EndPoint)
|
14
|
+
(self.id == other.id) && (self.type == other.class.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
#Factory Construction method that creates an EndPoint instance from a model
|
18
|
+
def self.from_resource(resource)
|
19
|
+
self.new(resource.id, resource.class.to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
#Factory Construction method that creates an EndPoint instance from a model if necessary
|
23
|
+
def self.from(obj)
|
24
|
+
return obj if obj.kind_of?(EndPoint)
|
25
|
+
self.from_resource(obj)
|
26
|
+
end
|
27
|
+
|
28
|
+
#Initializes the EndPoint instance with an id and type
|
29
|
+
def initialize(id, type)
|
30
|
+
@id = id
|
31
|
+
@type = type
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :id, :type
|
35
|
+
end
|
36
|
+
|
37
|
+
#Encapsulates information about the source of a link
|
38
|
+
class Source < EndPoint
|
39
|
+
#Factory Construction method that generates a source from a link
|
40
|
+
def self.from_edge(edge)
|
41
|
+
self.new(edge.ancestor_id, edge.ancestor_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#Encapsulates information about the sink (destination) of a link
|
46
|
+
class Sink < EndPoint
|
47
|
+
#Factory Construction method that generates a sink from a link
|
48
|
+
def self.from_edge(edge)
|
49
|
+
self.new(edge.descendant_id, edge.descendant_type)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#Contains class methods that extend the link model for polymorphic DAGs
|
54
|
+
#Builds a hash that describes a link from a source and a sink
|
55
|
+
def conditions_for(source, sink)
|
56
|
+
{
|
57
|
+
ancestor_id_column_name => source.id,
|
58
|
+
ancestor_type_column_name => source.type,
|
59
|
+
descendant_id_column_name => sink.id,
|
60
|
+
descendant_type_column_name => sink.type
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
#Instance methods included into link model for a polymorphic DAG
|
65
|
+
module PolyEdgeInstanceMethods
|
66
|
+
def ancestor_type
|
67
|
+
self[ancestor_type_column_name]
|
68
|
+
end
|
69
|
+
|
70
|
+
def descendant_type
|
71
|
+
self[descendant_type_column_name]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/dag/standard.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Dag
|
2
|
+
module Standard
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :include, NonPolyEdgeInstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
#Encapsulates the necessary information about a graph node
|
9
|
+
class EndPoint
|
10
|
+
#Does an endpoint match another endpoint or model instance
|
11
|
+
def matches?(other)
|
12
|
+
self.id == other.id
|
13
|
+
end
|
14
|
+
|
15
|
+
#Factory Construction method that creates an endpoint from a model
|
16
|
+
def self.from_resource(resource)
|
17
|
+
self.new(resource.id)
|
18
|
+
end
|
19
|
+
|
20
|
+
#Factory Construction method that creates an endpoint from a model if necessary
|
21
|
+
def self.from(obj)
|
22
|
+
return obj if obj.kind_of?(EndPoint)
|
23
|
+
self.from_resource(obj)
|
24
|
+
end
|
25
|
+
|
26
|
+
#Initializes an endpoint based on an Id
|
27
|
+
def initialize(id)
|
28
|
+
@id = id
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :id
|
32
|
+
end
|
33
|
+
|
34
|
+
#Encapsulates information about the source of a link
|
35
|
+
class Source < EndPoint
|
36
|
+
#Factory Construction method creates a source instance from a link
|
37
|
+
def self.from_edge(edge)
|
38
|
+
self.new(edge.ancestor_id)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
#Encapsulates information about the sink of a link
|
42
|
+
class Sink < EndPoint
|
43
|
+
#Factory Construction method creates a sink instance from a link
|
44
|
+
def self.from_edge(edge)
|
45
|
+
self.new(edge.descendant_id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#Builds a hash that describes a link from a source and a sink
|
50
|
+
def conditions_for(source, sink)
|
51
|
+
{
|
52
|
+
ancestor_id_column_name => source.id,
|
53
|
+
descendant_id_column_name => sink.id
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
#Instance methods included into the link model for a non-polymorphic DAG
|
58
|
+
module NonPolyEdgeInstanceMethods
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dag
|
2
|
+
|
3
|
+
#Validations on model instance creation. Ensures no duplicate links, no cycles, and correct count and direct attributes
|
4
|
+
class CreateCorrectnessValidator < ActiveModel::Validator
|
5
|
+
|
6
|
+
def validate(record)
|
7
|
+
record.errors[:base] << 'Link already exists between these points' if has_duplicates(record)
|
8
|
+
record.errors[:base] << 'Link already exists in the opposite direction' if has_long_cycles(record)
|
9
|
+
record.errors[:base] << 'Link must start and end in different places' if has_short_cycles(record)
|
10
|
+
cnt = check_possible(record)
|
11
|
+
record.errors[:base] << 'Cannot create a direct link with a count other than 0' if cnt == 1
|
12
|
+
record.errors[:base] << 'Cannot create an indirect link with a count less than 1' if cnt == 2
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
#check for duplicates
|
18
|
+
def has_duplicates(record)
|
19
|
+
record.class.find_link(record.source, record.sink)
|
20
|
+
end
|
21
|
+
|
22
|
+
#check for long cycles
|
23
|
+
def has_long_cycles(record)
|
24
|
+
record.class.find_link(record.sink, record.source)
|
25
|
+
end
|
26
|
+
|
27
|
+
#check for short cycles
|
28
|
+
def has_short_cycles(record)
|
29
|
+
record.sink.matches?(record.source)
|
30
|
+
end
|
31
|
+
|
32
|
+
#check not impossible
|
33
|
+
def check_possible(record)
|
34
|
+
record.direct? ? (record.count != 0 ? 1 : 0) : (record.count < 1 ? 2 : 0)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#Validations on update. Makes sure that something changed, that not making a lonely link indirect, and count is correct.
|
39
|
+
class UpdateCorrectnessValidator < ActiveModel::Validator
|
40
|
+
|
41
|
+
def validate(record)
|
42
|
+
record.errors[:base] << "No changes" unless record.changed?
|
43
|
+
record.errors[:base] << "Do not manually change the count value" if manual_change(record)
|
44
|
+
record.errors[:base] << "Cannot make a direct link with count 1 indirect" if direct_indirect(record)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def manual_change(record)
|
50
|
+
record.direct_changed? && record.count_changed?
|
51
|
+
end
|
52
|
+
|
53
|
+
def direct_indirect(record)
|
54
|
+
record.direct_changed? && !record.direct? && record.count == 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/test/dag_test.rb
CHANGED
data/test/database.test
ADDED
Binary file
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 2
|
7
|
+
- 5
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 2.0.1
|
9
|
+
version: 2.5.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Matthew Leventi
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-10-
|
18
|
+
date: 2010-10-11 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -28,8 +28,16 @@ extensions: []
|
|
28
28
|
extra_rdoc_files:
|
29
29
|
- README.rdoc
|
30
30
|
files:
|
31
|
-
- lib/
|
31
|
+
- lib/dag.rb
|
32
|
+
- lib/dag/columns.rb
|
33
|
+
- lib/dag/dag.rb
|
34
|
+
- lib/dag/edges.rb
|
35
|
+
- lib/dag/poly_columns.rb
|
36
|
+
- lib/dag/polymorphic.rb
|
37
|
+
- lib/dag/standard.rb
|
38
|
+
- lib/dag/validators.rb
|
32
39
|
- test/dag_test.rb
|
40
|
+
- test/database.test
|
33
41
|
- README.rdoc
|
34
42
|
has_rdoc: true
|
35
43
|
homepage: http://github.com/resgraph/acts-as-dag
|
@@ -1,803 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Acts
|
3
|
-
module Dag
|
4
|
-
def self.included(base)
|
5
|
-
base.extend(SingletonMethods)
|
6
|
-
end
|
7
|
-
|
8
|
-
module SingletonMethods
|
9
|
-
#Sets up a model to act as dag links for models specified under the :for option
|
10
|
-
def acts_as_dag_links(options = {})
|
11
|
-
conf = {
|
12
|
-
:ancestor_id_column => 'ancestor_id',
|
13
|
-
:ancestor_type_column => 'ancestor_type',
|
14
|
-
:descendant_id_column => 'descendant_id',
|
15
|
-
:descendant_type_column => 'descendant_type',
|
16
|
-
:direct_column => 'direct',
|
17
|
-
:count_column => 'count',
|
18
|
-
:polymorphic => false,
|
19
|
-
:node_class_name => nil}
|
20
|
-
conf.update(options)
|
21
|
-
|
22
|
-
unless conf[:polymorphic]
|
23
|
-
if conf[:node_class_name].nil?
|
24
|
-
raise ActiveRecord::ActiveRecordError, 'Non-polymorphic graphs need to specify :node_class_name with the receiving class like belong_to'
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
write_inheritable_attribute :acts_as_dag_options, conf
|
29
|
-
class_inheritable_reader :acts_as_dag_options
|
30
|
-
|
31
|
-
extend Columns
|
32
|
-
include Columns
|
33
|
-
|
34
|
-
#access to _changed? and _was for (edge,count) if not default
|
35
|
-
unless direct_column_name == 'direct'
|
36
|
-
module_eval <<-"end_eval", __FILE__, __LINE__
|
37
|
-
def direct_changed?
|
38
|
-
self.#{direct_column_name}_changed?
|
39
|
-
end
|
40
|
-
def direct_was
|
41
|
-
self.#{direct_column_name}_was
|
42
|
-
end
|
43
|
-
end_eval
|
44
|
-
end
|
45
|
-
|
46
|
-
unless count_column_name == 'count'
|
47
|
-
module_eval <<-"end_eval", __FILE__, __LINE__
|
48
|
-
def count_changed?
|
49
|
-
self.#{count_column_name}_changed?
|
50
|
-
end
|
51
|
-
def count_was
|
52
|
-
self.#{count_column_name}_was
|
53
|
-
end
|
54
|
-
end_eval
|
55
|
-
end
|
56
|
-
|
57
|
-
internal_columns = [ancestor_id_column_name, descendant_id_column_name]
|
58
|
-
edge_class_name = self.to_s
|
59
|
-
|
60
|
-
direct_column_name.intern
|
61
|
-
count_column_name.intern
|
62
|
-
|
63
|
-
#links to ancestor and descendant
|
64
|
-
if acts_as_dag_polymorphic?
|
65
|
-
extend PolyColumns
|
66
|
-
include PolyColumns
|
67
|
-
|
68
|
-
internal_columns << ancestor_type_column_name
|
69
|
-
internal_columns << descendant_type_column_name
|
70
|
-
|
71
|
-
belongs_to :ancestor, :polymorphic => true
|
72
|
-
belongs_to :descendant, :polymorphic => true
|
73
|
-
|
74
|
-
validates_presence_of ancestor_type_column_name, descendant_type_column_name
|
75
|
-
validates_uniqueness_of ancestor_id_column_name, :scope => [ancestor_type_column_name, descendant_type_column_name, descendant_id_column_name]
|
76
|
-
|
77
|
-
scope :with_ancestor, lambda { |ancestor| {:conditions => {ancestor_id_column_name => ancestor.id, ancestor_type_column_name => ancestor.class.to_s}} }
|
78
|
-
scope :with_descendant, lambda { |descendant| {:conditions => {descendant_id_column_name => descendant.id, descendant_type_column_name => descendant.class.to_s}} }
|
79
|
-
|
80
|
-
scope :with_ancestor_point, lambda { |point| {:conditions => {ancestor_id_column_name => point.id, ancestor_type_column_name => point.type}} }
|
81
|
-
scope :with_descendant_point, lambda { |point| {:conditions => {descendant_id_column_name => point.id, descendant_type_column_name => point.type}} }
|
82
|
-
|
83
|
-
extend PolyEdgeClassMethods
|
84
|
-
include PolyEdgeClasses
|
85
|
-
include PolyEdgeInstanceMethods
|
86
|
-
else
|
87
|
-
belongs_to :ancestor, :foreign_key => ancestor_id_column_name, :class_name => acts_as_dag_options[:node_class_name]
|
88
|
-
belongs_to :descendant, :foreign_key => descendant_id_column_name, :class_name => acts_as_dag_options[:node_class_name]
|
89
|
-
|
90
|
-
validates_uniqueness_of ancestor_id_column_name, :scope => [descendant_id_column_name]
|
91
|
-
|
92
|
-
scope :with_ancestor, lambda { |ancestor| {:conditions => {ancestor_id_column_name => ancestor.id}} }
|
93
|
-
scope :with_descendant, lambda { |descendant| {:conditions => {descendant_id_column_name => descendant.id}} }
|
94
|
-
|
95
|
-
scope :with_ancestor_point, lambda { |point| {:conditions => {ancestor_id_column_name => point.id}} }
|
96
|
-
scope :with_descendant_point, lambda { |point| {:conditions => {descendant_id_column_name => point.id}} }
|
97
|
-
|
98
|
-
extend NonPolyEdgeClassMethods
|
99
|
-
include NonPolyEdgeClasses
|
100
|
-
include NonPolyEdgeInstanceMethods
|
101
|
-
end
|
102
|
-
|
103
|
-
scope :direct, :conditions => {:direct => true}
|
104
|
-
scope :indirect, :conditions => {:direct => false}
|
105
|
-
|
106
|
-
scope :ancestor_nodes, :joins => :ancestor
|
107
|
-
scope :descendant_nodes, :joins => :descendant
|
108
|
-
|
109
|
-
validates_presence_of ancestor_id_column_name, descendant_id_column_name
|
110
|
-
validates_numericality_of ancestor_id_column_name, descendant_id_column_name
|
111
|
-
|
112
|
-
extend EdgeClassMethods
|
113
|
-
include EdgeInstanceMethods
|
114
|
-
|
115
|
-
before_destroy :destroyable!, :perpetuate
|
116
|
-
before_save :perpetuate
|
117
|
-
before_validation :field_check, :fill_defaults, :on => :update
|
118
|
-
before_validation :fill_defaults, :on => :create
|
119
|
-
validate :create_validation, :on => :create
|
120
|
-
validate :update_validation, :on => :update
|
121
|
-
|
122
|
-
|
123
|
-
#internal fields
|
124
|
-
code = 'def field_check ' + "\n"
|
125
|
-
internal_columns.each do |column|
|
126
|
-
code += "if " + column + "_changed? \n" + ' raise ActiveRecord::ActiveRecordError, "Column: '+column+' cannot be changed for an existing record it is immutable"' + "\n end \n"
|
127
|
-
end
|
128
|
-
code += 'end'
|
129
|
-
module_eval code
|
130
|
-
|
131
|
-
[count_column_name].each do |column|
|
132
|
-
module_eval <<-"end_eval", __FILE__, __LINE__
|
133
|
-
def #{column}=(x)
|
134
|
-
raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_dag code."
|
135
|
-
end
|
136
|
-
end_eval
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def has_dag_links(options = {})
|
141
|
-
conf = {
|
142
|
-
:class_name => nil,
|
143
|
-
:prefix => '',
|
144
|
-
:ancestor_class_names => [],
|
145
|
-
:descendant_class_names => []
|
146
|
-
}
|
147
|
-
conf.update(options)
|
148
|
-
|
149
|
-
#check that class_name is filled
|
150
|
-
if conf[:link_class_name].nil?
|
151
|
-
raise ActiveRecord::ActiveRecordError, "has_dag must be provided with :link_class_name option"
|
152
|
-
end
|
153
|
-
|
154
|
-
#add trailing '_' to prefix
|
155
|
-
unless conf[:prefix] == ''
|
156
|
-
conf[:prefix] += '_'
|
157
|
-
end
|
158
|
-
|
159
|
-
prefix = conf[:prefix]
|
160
|
-
dag_link_class_name = conf[:link_class_name]
|
161
|
-
dag_link_class = conf[:link_class_name].constantize
|
162
|
-
|
163
|
-
if dag_link_class.acts_as_dag_polymorphic?
|
164
|
-
self.class_eval <<-EOL
|
165
|
-
has_many :#{prefix}links_as_ancestor, :as => :ancestor, :class_name => '#{dag_link_class_name}'
|
166
|
-
has_many :#{prefix}links_as_descendant, :as => :descendant, :class_name => '#{dag_link_class_name}'
|
167
|
-
|
168
|
-
has_many :#{prefix}links_as_parent, :as => :ancestor, :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.direct_column_name}' => true}
|
169
|
-
has_many :#{prefix}links_as_child, :as => :descendant, :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.direct_column_name}' => true}
|
170
|
-
|
171
|
-
EOL
|
172
|
-
|
173
|
-
ancestor_table_names = []
|
174
|
-
parent_table_names = []
|
175
|
-
conf[:ancestor_class_names].each do |class_name|
|
176
|
-
table_name = class_name.tableize
|
177
|
-
self.class_eval <<-EOL2
|
178
|
-
has_many :#{prefix}links_as_descendant_for_#{table_name}, :as => :descendant, :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.ancestor_type_column_name}' => '#{class_name}'}
|
179
|
-
has_many :#{prefix}ancestor_#{table_name}, :through => :#{prefix}links_as_descendant_for_#{table_name}, :source => :ancestor, :source_type => '#{class_name}'
|
180
|
-
has_many :#{prefix}links_as_child_for_#{table_name}, :as => :descendant, :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.ancestor_type_column_name}' => '#{class_name}','#{dag_link_class.direct_column_name}' => true}
|
181
|
-
has_many :#{prefix}parent_#{table_name}, :through => :#{prefix}links_as_child_for_#{table_name}, :source => :ancestor, :source_type => '#{class_name}'
|
182
|
-
|
183
|
-
def #{prefix}root_for_#{table_name}?
|
184
|
-
return self.links_as_descendant_for_#{table_name}.empty?
|
185
|
-
end
|
186
|
-
EOL2
|
187
|
-
ancestor_table_names << (prefix+'ancestor_'+table_name)
|
188
|
-
parent_table_names << (prefix+'parent_'+table_name)
|
189
|
-
unless conf[:descendant_class_names].include?(class_name)
|
190
|
-
#this apparently is only one way is we can create some aliases making things easier
|
191
|
-
self.class_eval "has_many :#{prefix}#{table_name}, :through => :#{prefix}links_as_descendant_for_#{table_name}, :source => :ancestor, :source_type => '#{class_name}'"
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
unless conf[:ancestor_class_names].empty?
|
196
|
-
self.class_eval <<-EOL25
|
197
|
-
def #{prefix}ancestors
|
198
|
-
return #{ancestor_table_names.join(' + ')}
|
199
|
-
end
|
200
|
-
def #{prefix}parents
|
201
|
-
return #{parent_table_names.join(' + ')}
|
202
|
-
end
|
203
|
-
EOL25
|
204
|
-
else
|
205
|
-
self.class_eval <<-EOL26
|
206
|
-
def #{prefix}ancestors
|
207
|
-
a = []
|
208
|
-
#{prefix}links_as_descendant.each do |link|
|
209
|
-
a << link.ancestor
|
210
|
-
end
|
211
|
-
return a
|
212
|
-
end
|
213
|
-
def #{prefix}parents
|
214
|
-
a = []
|
215
|
-
#{prefix}links_as_child.each do |link|
|
216
|
-
a << link.ancestor
|
217
|
-
end
|
218
|
-
return a
|
219
|
-
end
|
220
|
-
EOL26
|
221
|
-
end
|
222
|
-
|
223
|
-
descendant_table_names = []
|
224
|
-
child_table_names = []
|
225
|
-
conf[:descendant_class_names].each do |class_name|
|
226
|
-
table_name = class_name.tableize
|
227
|
-
self.class_eval <<-EOL3
|
228
|
-
has_many :#{prefix}links_as_ancestor_for_#{table_name}, :as => :ancestor, :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.descendant_type_column_name}' => '#{class_name}'}
|
229
|
-
has_many :#{prefix}descendant_#{table_name}, :through => :#{prefix}links_as_ancestor_for_#{table_name}, :source => :descendant, :source_type => '#{class_name}'
|
230
|
-
|
231
|
-
has_many :#{prefix}links_as_parent_for_#{table_name}, :as => :ancestor, :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.descendant_type_column_name}' => '#{class_name}','#{dag_link_class.direct_column_name}' => true}
|
232
|
-
has_many :#{prefix}child_#{table_name}, :through => :#{prefix}links_as_parent_for_#{table_name}, :source => :descendant, :source_type => '#{class_name}'
|
233
|
-
|
234
|
-
def #{prefix}leaf_for_#{table_name}?
|
235
|
-
return self.links_as_ancestor_for_#{table_name}.empty?
|
236
|
-
end
|
237
|
-
EOL3
|
238
|
-
descendant_table_names << (prefix+'descendant_'+table_name)
|
239
|
-
child_table_names << (prefix+'child_'+table_name)
|
240
|
-
unless conf[:ancestor_class_names].include?(class_name)
|
241
|
-
self.class_eval "has_many :#{prefix}#{table_name}, :through => :#{prefix}links_as_ancestor_for_#{table_name}, :source => :descendant, :source_type => '#{class_name}'"
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
unless conf[:descendant_class_names].empty?
|
246
|
-
self.class_eval <<-EOL35
|
247
|
-
def #{prefix}descendants
|
248
|
-
return #{descendant_table_names.join(' + ')}
|
249
|
-
end
|
250
|
-
def #{prefix}children
|
251
|
-
return #{child_table_names.join(' + ')}
|
252
|
-
end
|
253
|
-
EOL35
|
254
|
-
else
|
255
|
-
self.class_eval <<-EOL36
|
256
|
-
def #{prefix}descendants
|
257
|
-
d = []
|
258
|
-
#{prefix}links_as_ancestor.each do |link|
|
259
|
-
d << link.descendant
|
260
|
-
end
|
261
|
-
return d
|
262
|
-
end
|
263
|
-
def #{prefix}children
|
264
|
-
d = []
|
265
|
-
#{prefix}links_as_parent.each do |link|
|
266
|
-
d << link.descendant
|
267
|
-
end
|
268
|
-
return d
|
269
|
-
end
|
270
|
-
EOL36
|
271
|
-
end
|
272
|
-
else
|
273
|
-
self.class_eval <<-EOL4
|
274
|
-
has_many :#{prefix}links_as_ancestor, :foreign_key => '#{dag_link_class.ancestor_id_column_name}', :class_name => '#{dag_link_class_name}'
|
275
|
-
has_many :#{prefix}links_as_descendant, :foreign_key => '#{dag_link_class.descendant_id_column_name}', :class_name => '#{dag_link_class_name}'
|
276
|
-
|
277
|
-
has_many :#{prefix}ancestors, :through => :#{prefix}links_as_descendant, :source => :ancestor
|
278
|
-
has_many :#{prefix}descendants, :through => :#{prefix}links_as_ancestor, :source => :descendant
|
279
|
-
|
280
|
-
has_many :#{prefix}links_as_parent, :foreign_key => '#{dag_link_class.ancestor_id_column_name}', :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.direct_column_name}' => true}
|
281
|
-
has_many :#{prefix}links_as_child, :foreign_key => '#{dag_link_class.descendant_id_column_name}', :class_name => '#{dag_link_class_name}', :conditions => {'#{dag_link_class.direct_column_name}' => true}
|
282
|
-
|
283
|
-
has_many :#{prefix}parents, :through => :#{prefix}links_as_child, :source => :ancestor
|
284
|
-
has_many :#{prefix}children, :through => :#{prefix}links_as_parent, :source => :descendant
|
285
|
-
|
286
|
-
EOL4
|
287
|
-
end
|
288
|
-
self.class_eval <<-EOL5
|
289
|
-
def #{prefix}leaf?
|
290
|
-
return self.#{prefix}links_as_ancestor.empty?
|
291
|
-
end
|
292
|
-
def #{prefix}root?
|
293
|
-
return self.#{prefix}links_as_descendant.empty?
|
294
|
-
end
|
295
|
-
EOL5
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
|
300
|
-
#Methods that show the columns for polymorphic DAGs
|
301
|
-
module PolyColumns
|
302
|
-
def ancestor_type_column_name
|
303
|
-
acts_as_dag_options[:ancestor_type_column]
|
304
|
-
end
|
305
|
-
|
306
|
-
def descendant_type_column_name
|
307
|
-
acts_as_dag_options[:descendant_type_column]
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
#Methods that show columns
|
312
|
-
module Columns
|
313
|
-
def ancestor_id_column_name
|
314
|
-
acts_as_dag_options[:ancestor_id_column]
|
315
|
-
end
|
316
|
-
|
317
|
-
def descendant_id_column_name
|
318
|
-
acts_as_dag_options[:descendant_id_column]
|
319
|
-
end
|
320
|
-
|
321
|
-
def direct_column_name
|
322
|
-
acts_as_dag_options[:direct_column]
|
323
|
-
end
|
324
|
-
|
325
|
-
def count_column_name
|
326
|
-
acts_as_dag_options[:count_column]
|
327
|
-
end
|
328
|
-
|
329
|
-
def acts_as_dag_polymorphic?
|
330
|
-
acts_as_dag_options[:polymorphic]
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
#Contains class methods that extend the link model for polymorphic DAGs
|
335
|
-
module PolyEdgeClassMethods
|
336
|
-
#Builds a hash that describes a link from a source and a sink
|
337
|
-
def conditions_for(source, sink)
|
338
|
-
{
|
339
|
-
ancestor_id_column_name => source.id,
|
340
|
-
ancestor_type_column_name => source.type,
|
341
|
-
descendant_id_column_name => sink.id,
|
342
|
-
descendant_type_column_name => sink.type
|
343
|
-
}
|
344
|
-
end
|
345
|
-
end
|
346
|
-
#Contains nested classes in the link model for polymorphic DAGs
|
347
|
-
module PolyEdgeClasses
|
348
|
-
#Encapsulates the necessary information about a graph node
|
349
|
-
class EndPoint
|
350
|
-
#Does the endpoint match a model or another endpoint
|
351
|
-
def matches?(other)
|
352
|
-
return (self.id == other.id) && (self.type == other.type) if other.is_a?(EndPoint)
|
353
|
-
return (self.id == other.id) && (self.type == other.class.to_s)
|
354
|
-
end
|
355
|
-
|
356
|
-
#Factory Construction method that creates an EndPoint instance from a model
|
357
|
-
def self.from_resource(resource)
|
358
|
-
self.new(resource.id, resource.class.to_s)
|
359
|
-
end
|
360
|
-
|
361
|
-
#Factory Construction method that creates an EndPoint instance from a model if necessary
|
362
|
-
def self.from(obj)
|
363
|
-
return obj if obj.kind_of?(EndPoint)
|
364
|
-
return self.from_resource(obj)
|
365
|
-
end
|
366
|
-
|
367
|
-
#Initializes the EndPoint instance with an id and type
|
368
|
-
def initialize(id, type)
|
369
|
-
@id = id
|
370
|
-
@type = type
|
371
|
-
end
|
372
|
-
|
373
|
-
attr_reader :id, :type
|
374
|
-
end
|
375
|
-
|
376
|
-
#Encapsulates information about the source of a link
|
377
|
-
class Source < EndPoint
|
378
|
-
#Factory Construction method that generates a source from a link
|
379
|
-
def self.from_edge(edge)
|
380
|
-
self.new(edge.ancestor_id, edge.ancestor_type)
|
381
|
-
end
|
382
|
-
end
|
383
|
-
|
384
|
-
#Encapsulates information about the sink (destination) of a link
|
385
|
-
class Sink < EndPoint
|
386
|
-
#Factory Construction method that generates a sink from a link
|
387
|
-
def self.from_edge(edge)
|
388
|
-
self.new(edge.descendant_id, edge.descendant_type)
|
389
|
-
end
|
390
|
-
end
|
391
|
-
end
|
392
|
-
|
393
|
-
#Contains class methods that extend the link model for a nonpolymorphic DAG
|
394
|
-
module NonPolyEdgeClassMethods
|
395
|
-
#Builds a hash that describes a link from a source and a sink
|
396
|
-
def conditions_for(source, sink)
|
397
|
-
{
|
398
|
-
ancestor_id_column_name => source.id,
|
399
|
-
descendant_id_column_name => sink.id
|
400
|
-
}
|
401
|
-
end
|
402
|
-
end
|
403
|
-
#Contains nested classes in the link model for a nonpolymorphic DAG
|
404
|
-
module NonPolyEdgeClasses
|
405
|
-
#Encapsulates the necessary information about a graph node
|
406
|
-
class EndPoint
|
407
|
-
#Does an endpoint match another endpoint or model instance
|
408
|
-
def matches?(other)
|
409
|
-
return (self.id == other.id)
|
410
|
-
end
|
411
|
-
|
412
|
-
#Factory Construction method that creates an endpoint from a model
|
413
|
-
def self.from_resource(resource)
|
414
|
-
self.new(resource.id)
|
415
|
-
end
|
416
|
-
|
417
|
-
#Factory Construction method that creates an endpoint from a model if necessary
|
418
|
-
def self.from(obj)
|
419
|
-
return obj if obj.kind_of?(EndPoint)
|
420
|
-
return self.from_resource(obj)
|
421
|
-
end
|
422
|
-
|
423
|
-
#Initializes an endpoint based on an Id
|
424
|
-
def initialize(id)
|
425
|
-
@id = id
|
426
|
-
end
|
427
|
-
|
428
|
-
attr_reader :id
|
429
|
-
end
|
430
|
-
|
431
|
-
#Encapsulates information about the source of a link
|
432
|
-
class Source < EndPoint
|
433
|
-
#Factory Construction method creates a source instance from a link
|
434
|
-
def self.from_edge(edge)
|
435
|
-
return self.new(edge.ancestor_id)
|
436
|
-
end
|
437
|
-
end
|
438
|
-
#Encapsulates information about the sink of a link
|
439
|
-
class Sink < EndPoint
|
440
|
-
#Factory Construction method creates a sink instance from a link
|
441
|
-
def self.from_edge(edge)
|
442
|
-
return self.new(edge.descendant_id)
|
443
|
-
end
|
444
|
-
end
|
445
|
-
end
|
446
|
-
|
447
|
-
#Class methods that extend the link model for both polymorphic and nonpolymorphic graphs
|
448
|
-
module EdgeClassMethods
|
449
|
-
|
450
|
-
#Returns a new edge between two points
|
451
|
-
def build_edge(ancestor, descendant)
|
452
|
-
source = self::EndPoint.from(ancestor)
|
453
|
-
sink = self::EndPoint.from(descendant)
|
454
|
-
conditions = self.conditions_for(source, sink)
|
455
|
-
path = self.new(conditions)
|
456
|
-
path.make_direct
|
457
|
-
return path
|
458
|
-
end
|
459
|
-
|
460
|
-
#Finds an edge between two points, Must be direct
|
461
|
-
def find_edge(ancestor, descendant)
|
462
|
-
source = self::EndPoint.from(ancestor)
|
463
|
-
sink = self::EndPoint.from(descendant)
|
464
|
-
edge = self.find(:first, :conditions => self.conditions_for(source, sink).merge!({direct_column_name => true}))
|
465
|
-
return edge
|
466
|
-
end
|
467
|
-
|
468
|
-
#Finds a link between two points
|
469
|
-
def find_link(ancestor, descendant)
|
470
|
-
source = self::EndPoint.from(ancestor)
|
471
|
-
sink = self::EndPoint.from(descendant)
|
472
|
-
link = self.find(:first, :conditions => self.conditions_for(source, sink))
|
473
|
-
return link
|
474
|
-
end
|
475
|
-
|
476
|
-
#Finds or builds an edge between two points
|
477
|
-
def find_or_build_edge(ancestor, descendant)
|
478
|
-
edge = self.find_edge(ancestor, descendant)
|
479
|
-
return edge unless edge.nil?
|
480
|
-
return build_edge(ancestor, descendant)
|
481
|
-
end
|
482
|
-
|
483
|
-
#Creates an edge between two points using save
|
484
|
-
def create_edge(ancestor, descendant)
|
485
|
-
link = self.find_link(ancestor, descendant)
|
486
|
-
if link.nil?
|
487
|
-
edge = self.build_edge(ancestor, descendant)
|
488
|
-
return edge.save
|
489
|
-
else
|
490
|
-
link.make_direct
|
491
|
-
return link.save
|
492
|
-
end
|
493
|
-
end
|
494
|
-
|
495
|
-
#Creates an edge between two points using save! Returns created edge
|
496
|
-
def create_edge!(ancestor, descendant)
|
497
|
-
link = self.find_link(ancestor, descendant)
|
498
|
-
if link.nil?
|
499
|
-
edge = self.build_edge(ancestor, descendant)
|
500
|
-
edge.save!
|
501
|
-
return edge
|
502
|
-
else
|
503
|
-
link.make_direct
|
504
|
-
link.save!
|
505
|
-
return link
|
506
|
-
end
|
507
|
-
end
|
508
|
-
|
509
|
-
#Alias for create_edge
|
510
|
-
def connect(ancestor, descendant)
|
511
|
-
return self.create_edge(ancestor, descendant)
|
512
|
-
end
|
513
|
-
|
514
|
-
#Alias for create_edge!
|
515
|
-
def connect!(ancestor, descendant)
|
516
|
-
return self.create_edge!(ancestor, descendant)
|
517
|
-
end
|
518
|
-
|
519
|
-
#Determines if a link exists between two points
|
520
|
-
def connected?(ancestor, descendant)
|
521
|
-
return !self.find_link(ancestor, descendant).nil?
|
522
|
-
end
|
523
|
-
|
524
|
-
#Finds the longest path between ancestor and descendant returning as an array
|
525
|
-
def longest_path_between(ancestor, descendant, path=[])
|
526
|
-
longest = []
|
527
|
-
ancestor.children.each do |child|
|
528
|
-
if child == descendent
|
529
|
-
temp = path.clone
|
530
|
-
temp << child
|
531
|
-
if temp.length > longest.length
|
532
|
-
longest = temp
|
533
|
-
end
|
534
|
-
elsif self.connected?(child, descendant)
|
535
|
-
temp = path.clone
|
536
|
-
temp << child
|
537
|
-
temp = self.longest_path_between(child, descendant, temp)
|
538
|
-
if temp.length > longest.length
|
539
|
-
longest = temp
|
540
|
-
end
|
541
|
-
end
|
542
|
-
end
|
543
|
-
return longest
|
544
|
-
end
|
545
|
-
|
546
|
-
#Determines if an edge exists between two points
|
547
|
-
def edge?(ancestor, descendant)
|
548
|
-
return !self.find_edge(ancestor, descendant).nil?
|
549
|
-
end
|
550
|
-
|
551
|
-
#Alias for edge
|
552
|
-
def direct?(ancestor, descendant)
|
553
|
-
return self.edge?(ancestor, descendant)
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
#Instance methods included into link model for a polymorphic DAG
|
558
|
-
module PolyEdgeInstanceMethods
|
559
|
-
def ancestor_type
|
560
|
-
return self[ancestor_type_column_name]
|
561
|
-
end
|
562
|
-
|
563
|
-
def descendant_type
|
564
|
-
return self[descendant_type_column_name]
|
565
|
-
end
|
566
|
-
end
|
567
|
-
|
568
|
-
#Instance methods included into the link model for a non-polymorphic DAG
|
569
|
-
module NonPolyEdgeInstanceMethods
|
570
|
-
end
|
571
|
-
|
572
|
-
#Instance methods included into the link model for polymorphic and non-polymorphic DAGs
|
573
|
-
module EdgeInstanceMethods
|
574
|
-
|
575
|
-
attr_accessor :do_not_perpetuate
|
576
|
-
|
577
|
-
#Validations on model instance creation. Ensures no duplicate links, no cycles, and correct count and direct attributes
|
578
|
-
def create_validation
|
579
|
-
#make sure no duplicates
|
580
|
-
if self.class.find_link(self.source, self.sink)
|
581
|
-
self.errors.add(:base, 'Link already exists between these points')
|
582
|
-
end
|
583
|
-
#make sure no long cycles
|
584
|
-
if self.class.find_link(self.sink, self.source)
|
585
|
-
self.errors.add(:base, 'Link already exists in the opposite direction')
|
586
|
-
end
|
587
|
-
#make sure no short cycles
|
588
|
-
if self.sink.matches?(self.source)
|
589
|
-
self.errors.add(:base, 'Link must start and end in different places')
|
590
|
-
end
|
591
|
-
#make sure not impossible
|
592
|
-
if self.direct?
|
593
|
-
if self.count != 0
|
594
|
-
self.errors.add(:base, 'Cannot create a direct link with a count other than 0')
|
595
|
-
end
|
596
|
-
else
|
597
|
-
if self.count < 1
|
598
|
-
self.errors.add(:base, 'Cannot create an indirect link with a count less than 1')
|
599
|
-
end
|
600
|
-
end
|
601
|
-
end
|
602
|
-
|
603
|
-
#Validations on update. Makes sure that something changed, that not making a lonely link indirect, and count is correct.
|
604
|
-
def update_validation #validate_on_update
|
605
|
-
unless self.changed?
|
606
|
-
self.errors.add(:base, 'No changes')
|
607
|
-
end
|
608
|
-
if direct_changed?
|
609
|
-
if count_changed?
|
610
|
-
self.errors.add(:base, 'Do not manually change the count value')
|
611
|
-
end
|
612
|
-
if !self.direct?
|
613
|
-
if self.count == 1
|
614
|
-
self.errors.add(:base, 'Cannot make a direct link with count 1 indirect')
|
615
|
-
end
|
616
|
-
end
|
617
|
-
end
|
618
|
-
end
|
619
|
-
|
620
|
-
#Fill default direct and count values if necessary. In place of after_initialize method
|
621
|
-
def fill_defaults
|
622
|
-
self[direct_column_name] = true if self[direct_column_name].nil?
|
623
|
-
self[count_column_name] = 0 if self[count_column_name].nil?
|
624
|
-
end
|
625
|
-
|
626
|
-
#Whether the edge can be destroyed
|
627
|
-
def destroyable?
|
628
|
-
(self.count == 0) || (self.direct? && self.count == 1)
|
629
|
-
end
|
630
|
-
|
631
|
-
#Raises an exception if the edge is not destroyable. Otherwise makes the edge indirect before destruction to cleanup graph.
|
632
|
-
def destroyable!
|
633
|
-
raise ActiveRecord::ActiveRecordError, 'Cannot destroy this edge' unless destroyable?
|
634
|
-
#this triggers rewiring on destruction via perpetuate
|
635
|
-
if self.direct?
|
636
|
-
self[direct_column_name] = false
|
637
|
-
end
|
638
|
-
return true
|
639
|
-
end
|
640
|
-
|
641
|
-
#Analyzes the changes in a model instance and rewires as necessary.
|
642
|
-
def perpetuate
|
643
|
-
#flag set by links that were modified in association
|
644
|
-
return true if self.do_not_perpetuate
|
645
|
-
|
646
|
-
#if edge changed this was manually altered
|
647
|
-
if direct_changed?
|
648
|
-
if self.direct?
|
649
|
-
self[count_column_name] += 1
|
650
|
-
else
|
651
|
-
self[count_column_name] -= 1
|
652
|
-
end
|
653
|
-
self.wiring
|
654
|
-
end
|
655
|
-
end
|
656
|
-
|
657
|
-
#Id of the ancestor
|
658
|
-
def ancestor_id
|
659
|
-
return self[ancestor_id_column_name]
|
660
|
-
end
|
661
|
-
|
662
|
-
#Id of the descendant
|
663
|
-
def descendant_id
|
664
|
-
return self[descendant_id_column_name]
|
665
|
-
end
|
666
|
-
|
667
|
-
#Count of the edge, ie the edge exists in X ways
|
668
|
-
def count
|
669
|
-
return self[count_column_name]
|
670
|
-
end
|
671
|
-
|
672
|
-
#Changes the count of the edge. DO NOT CALL THIS OUTSIDE THE PLUGIN
|
673
|
-
def internal_count=(val)
|
674
|
-
self[count_column_name] = val
|
675
|
-
end
|
676
|
-
|
677
|
-
#Whether the link is direct, ie manually created
|
678
|
-
def direct?
|
679
|
-
return self[direct_column_name]
|
680
|
-
end
|
681
|
-
|
682
|
-
#Whether the link is an edge?
|
683
|
-
def edge?
|
684
|
-
return self[direct_column_name]
|
685
|
-
end
|
686
|
-
|
687
|
-
#Makes the link direct, ie an edge
|
688
|
-
def make_direct
|
689
|
-
self[direct_column_name] = true
|
690
|
-
end
|
691
|
-
|
692
|
-
#Makes an edge indirect, ie a link.
|
693
|
-
def make_indirect
|
694
|
-
self[direct_column_name] = false
|
695
|
-
end
|
696
|
-
|
697
|
-
#Source of the edge, creates if necessary
|
698
|
-
def source
|
699
|
-
@source = self.class::Source.from_edge(self) if @source.nil?
|
700
|
-
return @source
|
701
|
-
end
|
702
|
-
|
703
|
-
#Sink (destination) of the edge, creates if necessary
|
704
|
-
def sink
|
705
|
-
@sink = self.class::Sink.from_edge(self) if @sink.nil?
|
706
|
-
return @sink
|
707
|
-
end
|
708
|
-
|
709
|
-
#All links that end at the source
|
710
|
-
def links_to_source
|
711
|
-
self.class.with_descendant_point(self.source)
|
712
|
-
end
|
713
|
-
|
714
|
-
#all links that start from the sink
|
715
|
-
def links_from_sink
|
716
|
-
self.class.with_ancestor_point(self.sink)
|
717
|
-
end
|
718
|
-
|
719
|
-
protected
|
720
|
-
|
721
|
-
#Changes on a wire based on the count (destroy! or save!) (should not be called outside this plugin)
|
722
|
-
def push_associated_modification!(edge)
|
723
|
-
raise ActiveRecord::ActiveRecordError, 'Cannot modify ourself in this way' if edge == self
|
724
|
-
edge.do_not_perpetuate = true
|
725
|
-
if edge.count == 0
|
726
|
-
edge.destroy!
|
727
|
-
else
|
728
|
-
edge.save!
|
729
|
-
end
|
730
|
-
end
|
731
|
-
|
732
|
-
#Updates the wiring of edges that dependent on the current one
|
733
|
-
def rewire_crossing(above_leg, below_leg)
|
734
|
-
if above_leg.count_changed?
|
735
|
-
was = above_leg.count_was
|
736
|
-
was = 0 if was.nil?
|
737
|
-
above_leg_count = above_leg.count - was
|
738
|
-
if below_leg.count_changed?
|
739
|
-
raise ActiveRecord::ActiveRecordError, 'ERROR: both legs cannot 0 normal count change'
|
740
|
-
else
|
741
|
-
below_leg_count = below_leg.count
|
742
|
-
end
|
743
|
-
else
|
744
|
-
above_leg_count = above_leg.count
|
745
|
-
if below_leg.count_changed?
|
746
|
-
was = below_leg.count_was
|
747
|
-
was = 0 if was.nil?
|
748
|
-
below_leg_count = below_leg.count - was
|
749
|
-
else
|
750
|
-
raise ActiveRecord::ActiveRecordError, 'ERROR: both legs cannot have count changes'
|
751
|
-
end
|
752
|
-
end
|
753
|
-
count = above_leg_count * below_leg_count
|
754
|
-
source = above_leg.source
|
755
|
-
sink = below_leg.sink
|
756
|
-
bridging_leg = self.class.find_link(source, sink)
|
757
|
-
if bridging_leg.nil?
|
758
|
-
bridging_leg = self.class.new(self.class.conditions_for(source, sink))
|
759
|
-
bridging_leg.make_indirect
|
760
|
-
bridging_leg.internal_count = 0
|
761
|
-
end
|
762
|
-
bridging_leg.internal_count = bridging_leg.count + count
|
763
|
-
return bridging_leg
|
764
|
-
end
|
765
|
-
|
766
|
-
#Find the edges that need to be updated
|
767
|
-
def wiring
|
768
|
-
source = self.source
|
769
|
-
sink = self.sink
|
770
|
-
above_sources = []
|
771
|
-
self.links_to_source.each do |edge|
|
772
|
-
above_sources << edge.source
|
773
|
-
end
|
774
|
-
below_sinks = []
|
775
|
-
self.links_from_sink.each do |edge|
|
776
|
-
below_sinks << edge.sink
|
777
|
-
end
|
778
|
-
above_bridging_legs = []
|
779
|
-
#everything above me tied to my sink
|
780
|
-
above_sources.each do |above_source|
|
781
|
-
above_leg = self.class.find_link(above_source, source)
|
782
|
-
above_bridging_leg = self.rewire_crossing(above_leg, self)
|
783
|
-
above_bridging_legs << above_bridging_leg unless above_bridging_leg.nil?
|
784
|
-
end
|
785
|
-
|
786
|
-
#everything beneath me tied to my source
|
787
|
-
below_sinks.each do |below_sink|
|
788
|
-
below_leg = self.class.find_link(sink, below_sink)
|
789
|
-
below_bridging_leg = self.rewire_crossing(self, below_leg)
|
790
|
-
self.push_associated_modification!(below_bridging_leg)
|
791
|
-
above_bridging_legs.each do |above_bridging_leg|
|
792
|
-
long_leg = self.rewire_crossing(above_bridging_leg, below_leg)
|
793
|
-
self.push_associated_modification!(long_leg)
|
794
|
-
end
|
795
|
-
end
|
796
|
-
above_bridging_legs.each do |above_bridging_leg|
|
797
|
-
self.push_associated_modification!(above_bridging_leg)
|
798
|
-
end
|
799
|
-
end
|
800
|
-
end
|
801
|
-
end
|
802
|
-
end
|
803
|
-
end
|