dagnabit 2.2.6 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +105 -0
- data/LICENSE +1 -1
- data/MIGRATION.md +56 -0
- data/README.md +222 -0
- data/bin/dagnabit-test +11 -31
- data/db/connection.rb +3 -0
- data/{test/connections/native_postgresql → db/connections/postgresql}/connection.rb +6 -5
- data/db/models/edge.rb +6 -0
- data/db/models/other_edge.rb +6 -0
- data/db/models/other_vertex.rb +6 -0
- data/db/models/vertex.rb +6 -0
- data/db/schema.rb +32 -0
- data/lib/dagnabit.rb +6 -19
- data/lib/dagnabit/edge.rb +7 -0
- data/lib/dagnabit/edge/activation.rb +14 -0
- data/lib/dagnabit/edge/associations.rb +10 -0
- data/lib/dagnabit/edge/connectivity.rb +38 -0
- data/lib/dagnabit/graph.rb +143 -0
- data/lib/dagnabit/migration.rb +98 -0
- data/lib/dagnabit/version.rb +3 -0
- data/lib/dagnabit/vertex.rb +10 -0
- data/lib/dagnabit/vertex/activation.rb +19 -0
- data/lib/dagnabit/vertex/associations.rb +24 -0
- data/lib/dagnabit/vertex/bonding.rb +43 -0
- data/lib/dagnabit/vertex/connectivity.rb +130 -0
- data/lib/dagnabit/vertex/neighbors.rb +50 -0
- data/lib/dagnabit/vertex/settings.rb +56 -0
- metadata +94 -143
- data/.autotest +0 -5
- data/.document +0 -5
- data/.gitignore +0 -7
- data/Gemfile +0 -15
- data/Gemfile.lock +0 -38
- data/History.txt +0 -81
- data/README.rdoc +0 -202
- data/Rakefile +0 -52
- data/VERSION.yml +0 -5
- data/dagnabit.gemspec +0 -142
- data/init.rb +0 -1
- data/lib/dagnabit/activation.rb +0 -60
- data/lib/dagnabit/link/associations.rb +0 -18
- data/lib/dagnabit/link/class_methods.rb +0 -43
- data/lib/dagnabit/link/configuration.rb +0 -40
- data/lib/dagnabit/link/cycle_prevention.rb +0 -31
- data/lib/dagnabit/link/named_scopes.rb +0 -65
- data/lib/dagnabit/link/transitive_closure_link_model.rb +0 -86
- data/lib/dagnabit/link/transitive_closure_recalculation.rb +0 -17
- data/lib/dagnabit/link/transitive_closure_recalculation/on_create.rb +0 -104
- data/lib/dagnabit/link/transitive_closure_recalculation/on_destroy.rb +0 -125
- data/lib/dagnabit/link/transitive_closure_recalculation/on_update.rb +0 -17
- data/lib/dagnabit/link/transitive_closure_recalculation/utilities.rb +0 -56
- data/lib/dagnabit/link/validations.rb +0 -26
- data/lib/dagnabit/node/associations.rb +0 -84
- data/lib/dagnabit/node/class_methods.rb +0 -74
- data/lib/dagnabit/node/configuration.rb +0 -26
- data/lib/dagnabit/node/neighbors.rb +0 -73
- data/test/connections/native_sqlite3/connection.rb +0 -24
- data/test/dagnabit/link/test_associations.rb +0 -61
- data/test/dagnabit/link/test_class_methods.rb +0 -102
- data/test/dagnabit/link/test_configuration.rb +0 -38
- data/test/dagnabit/link/test_cycle_prevention.rb +0 -64
- data/test/dagnabit/link/test_named_scopes.rb +0 -32
- data/test/dagnabit/link/test_transitive_closure_link_model.rb +0 -69
- data/test/dagnabit/link/test_transitive_closure_recalculation.rb +0 -178
- data/test/dagnabit/link/test_validations.rb +0 -39
- data/test/dagnabit/node/test_associations.rb +0 -147
- data/test/dagnabit/node/test_class_methods.rb +0 -49
- data/test/dagnabit/node/test_configuration.rb +0 -29
- data/test/dagnabit/node/test_neighbors.rb +0 -91
- data/test/helper.rb +0 -26
- data/test/models/beta_node.rb +0 -3
- data/test/models/custom_data_link.rb +0 -4
- data/test/models/customized_link.rb +0 -7
- data/test/models/customized_link_node.rb +0 -4
- data/test/models/link.rb +0 -4
- data/test/models/node.rb +0 -3
- data/test/schema/schema.rb +0 -51
@@ -1,125 +0,0 @@
|
|
1
|
-
module Dagnabit
|
2
|
-
module Link
|
3
|
-
module TransitiveClosureRecalculation
|
4
|
-
module OnDestroy
|
5
|
-
def after_destroy
|
6
|
-
super
|
7
|
-
update_transitive_closure_for_destroy(*quoted_dag_link_values)
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def update_transitive_closure_for_destroy(aid, did, atype, dtype)
|
13
|
-
my_table = self.class.quoted_table_name
|
14
|
-
my_aid, my_did, my_atype, my_dtype = quoted_dag_link_column_names
|
15
|
-
tc = self.class.transitive_closure_table_name
|
16
|
-
tc_aid, tc_did, tc_atype, tc_dtype = quoted_dag_link_column_names
|
17
|
-
|
18
|
-
with_temporary_edge_tables('suspect', 'trusty', 'new') do |suspect, trusty, new|
|
19
|
-
connection.execute <<-END
|
20
|
-
INSERT INTO #{suspect} (#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype})
|
21
|
-
SELECT * FROM (
|
22
|
-
SELECT
|
23
|
-
TC1.#{tc_aid}, TC2.#{tc_did}, TC1.#{tc_atype}, TC2.#{tc_dtype}
|
24
|
-
FROM
|
25
|
-
#{tc} AS TC1, #{tc} AS TC2
|
26
|
-
WHERE
|
27
|
-
TC1.#{tc_did} = #{aid} AND TC2.#{tc_aid} = #{did}
|
28
|
-
AND
|
29
|
-
TC1.#{tc_dtype} = #{atype} AND TC2.#{tc_atype} = #{dtype}
|
30
|
-
UNION
|
31
|
-
SELECT
|
32
|
-
TC.#{tc_aid}, #{did}, TC.#{tc_atype}, #{dtype}
|
33
|
-
FROM
|
34
|
-
#{tc} AS TC
|
35
|
-
WHERE
|
36
|
-
TC.#{tc_did} = #{aid} AND TC.#{tc_dtype} = #{atype}
|
37
|
-
UNION
|
38
|
-
SELECT
|
39
|
-
#{aid}, TC.#{tc_did}, #{atype}, TC.#{tc_dtype}
|
40
|
-
FROM
|
41
|
-
#{tc} AS TC
|
42
|
-
WHERE
|
43
|
-
TC.#{tc_aid} = #{did} AND TC.#{tc_atype} = #{dtype}
|
44
|
-
UNION
|
45
|
-
SELECT
|
46
|
-
#{aid}, #{did}, #{atype}, #{dtype}
|
47
|
-
FROM
|
48
|
-
#{tc} AS TC
|
49
|
-
WHERE
|
50
|
-
TC.#{tc_aid} = #{aid} AND TC.#{tc_did} = #{did}
|
51
|
-
AND
|
52
|
-
TC.#{tc_atype} = #{atype} AND TC.#{tc_dtype} = #{dtype}
|
53
|
-
) AS tmp0
|
54
|
-
END
|
55
|
-
|
56
|
-
connection.execute <<-END
|
57
|
-
INSERT INTO #{trusty} (#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype})
|
58
|
-
SELECT
|
59
|
-
#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype}
|
60
|
-
FROM (
|
61
|
-
SELECT
|
62
|
-
#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype}
|
63
|
-
FROM
|
64
|
-
#{tc} AS TC
|
65
|
-
WHERE NOT EXISTS (
|
66
|
-
SELECT *
|
67
|
-
FROM
|
68
|
-
#{suspect} AS SUSPECT
|
69
|
-
WHERE
|
70
|
-
SUSPECT.#{tc_aid} = TC.#{tc_aid} AND SUSPECT.#{tc_did} = TC.#{tc_did}
|
71
|
-
AND
|
72
|
-
SUSPECT.#{tc_atype} = TC.#{tc_atype} AND SUSPECT.#{tc_dtype} = TC.#{tc_dtype}
|
73
|
-
)
|
74
|
-
UNION
|
75
|
-
SELECT
|
76
|
-
#{my_aid}, #{my_did}, #{my_atype}, #{my_dtype}
|
77
|
-
FROM
|
78
|
-
#{my_table} AS G
|
79
|
-
WHERE
|
80
|
-
NOT (G.#{my_aid} = #{aid} AND g.#{my_atype} = #{atype}
|
81
|
-
AND
|
82
|
-
G.#{my_did} = #{did} AND g.#{my_dtype} = #{dtype})
|
83
|
-
) AS tmp0
|
84
|
-
END
|
85
|
-
|
86
|
-
connection.execute <<-END
|
87
|
-
INSERT INTO #{new} (#{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype})
|
88
|
-
SELECT * FROM (
|
89
|
-
SELECT #{tc_aid}, #{tc_did}, #{tc_atype}, #{tc_dtype} FROM #{trusty}
|
90
|
-
UNION
|
91
|
-
SELECT
|
92
|
-
T1.#{tc_aid}, T2.#{tc_aid}, T1.#{tc_atype}, T2.#{tc_dtype}
|
93
|
-
FROM
|
94
|
-
#{trusty} T1, #{trusty} T2
|
95
|
-
WHERE
|
96
|
-
T1.#{tc_aid} = T2.#{tc_aid} AND T1.#{tc_atype} = T2.#{tc_dtype}
|
97
|
-
UNION
|
98
|
-
SELECT
|
99
|
-
T1.#{tc_aid}, T3.#{tc_aid}, T1.#{tc_atype}, T3.#{tc_dtype}
|
100
|
-
FROM
|
101
|
-
#{trusty} T1, #{trusty} T2, #{trusty} T3
|
102
|
-
WHERE
|
103
|
-
T1.#{tc_aid} = T2.#{tc_aid} AND T2.#{tc_aid} = T3.#{tc_aid}
|
104
|
-
AND
|
105
|
-
T1.#{tc_dtype} = T2.#{tc_atype} AND T2.#{tc_dtype} = T3.#{tc_atype}
|
106
|
-
) AS tmp0
|
107
|
-
END
|
108
|
-
|
109
|
-
connection.execute <<-END
|
110
|
-
DELETE FROM #{tc} WHERE NOT EXISTS (
|
111
|
-
SELECT *
|
112
|
-
FROM
|
113
|
-
#{new} T
|
114
|
-
WHERE
|
115
|
-
T.#{tc_aid} = #{tc}.#{tc_aid} AND T.#{tc_did} = #{tc}.#{tc_did}
|
116
|
-
AND
|
117
|
-
T.#{tc_atype} = #{tc}.#{tc_atype} AND T.#{tc_dtype} = #{tc}.#{tc_dtype}
|
118
|
-
)
|
119
|
-
END
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Dagnabit
|
2
|
-
module Link
|
3
|
-
module TransitiveClosureRecalculation
|
4
|
-
module OnUpdate
|
5
|
-
def after_update
|
6
|
-
current_values = dag_link_column_names.map { |n| connection.quote(send(n)) }
|
7
|
-
old_values = dag_link_column_names.map { |n| connection.quote(changes[n].try(:first) || send(n)) }
|
8
|
-
|
9
|
-
return unless current_values != old_values
|
10
|
-
|
11
|
-
update_transitive_closure_for_destroy(*old_values)
|
12
|
-
update_transitive_closure_for_create
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
module Dagnabit
|
2
|
-
module Link
|
3
|
-
module TransitiveClosureRecalculation
|
4
|
-
module Utilities
|
5
|
-
private
|
6
|
-
|
7
|
-
def quoted_dag_link_values
|
8
|
-
dag_link_column_names.map { |n| connection.quote(send(n)) }
|
9
|
-
end
|
10
|
-
|
11
|
-
def quoted_dag_link_column_names
|
12
|
-
dag_link_column_names.map { |n| connection.quote_column_name(n) }
|
13
|
-
end
|
14
|
-
|
15
|
-
def dag_link_column_names
|
16
|
-
[ self.class.ancestor_id_column,
|
17
|
-
self.class.descendant_id_column,
|
18
|
-
self.class.ancestor_type_column,
|
19
|
-
self.class.descendant_type_column ]
|
20
|
-
end
|
21
|
-
|
22
|
-
def all_quoted_column_values
|
23
|
-
all_column_names.map { |n| connection.quote(send(n)) }
|
24
|
-
end
|
25
|
-
|
26
|
-
def all_quoted_column_names
|
27
|
-
all_column_names.map { |n| connection.quote_column_name(n) }
|
28
|
-
end
|
29
|
-
|
30
|
-
def all_column_names
|
31
|
-
all_columns.map { |c| c.name }
|
32
|
-
end
|
33
|
-
|
34
|
-
def with_temporary_edge_tables(*tables, &block)
|
35
|
-
tables.each do |table|
|
36
|
-
connection.create_table(table, :temporary => true, :id => false) do |t|
|
37
|
-
all_columns.each do |c|
|
38
|
-
t.send(c.type, c.name)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
yield(tables.map { |t| connection.quote_table_name(t) })
|
44
|
-
|
45
|
-
tables.each do |table|
|
46
|
-
connection.drop_table table
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def all_columns
|
51
|
-
self.class.columns.reject { |c| c.name == 'id' || c.name == 'type' }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Dagnabit
|
2
|
-
module Link
|
3
|
-
#
|
4
|
-
# Basic validations on link models.
|
5
|
-
#
|
6
|
-
# This module only installs ancestor and descendant presence validations;
|
7
|
-
# the only basic requirement for a link is that it have a valid start point
|
8
|
-
# and a valid end point. We validate the presence of the +ancestor+ and
|
9
|
-
# +descendant+, instead of +ancestor_id+ and +descendant_id+, in order to
|
10
|
-
# permit scenarios like this:
|
11
|
-
#
|
12
|
-
# n1 = Node.new
|
13
|
-
# n2 = Node.new
|
14
|
-
# l = Link.new(:ancestor => n1, :descendant => n2)
|
15
|
-
# l.save # will save l, n1, and n2
|
16
|
-
#
|
17
|
-
module Validations
|
18
|
-
def self.extended(base)
|
19
|
-
base.send(:validates_presence_of, :ancestor)
|
20
|
-
base.send(:validates_presence_of, :descendant)
|
21
|
-
base.send(:validates_associated, :ancestor)
|
22
|
-
base.send(:validates_associated, :descendant)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,84 +0,0 @@
|
|
1
|
-
module Dagnabit
|
2
|
-
module Node
|
3
|
-
#
|
4
|
-
# Association macros added to node in a dagnabit dag.
|
5
|
-
#
|
6
|
-
# == Added associations
|
7
|
-
#
|
8
|
-
# * +links_as_parent+: Links for which this node is a parent.
|
9
|
-
# * +links_as_child+: Links for which this node is a child.
|
10
|
-
# * +links_as_ancestor+: Links for which this node is an ancestor.
|
11
|
-
# * +links_as_descendant+: Links for which this node is a descendant.
|
12
|
-
#
|
13
|
-
# == Illustration
|
14
|
-
#
|
15
|
-
# Suppose we have the following graph:
|
16
|
-
#
|
17
|
-
# n1
|
18
|
-
# |
|
19
|
-
# / \
|
20
|
-
# n2 n3
|
21
|
-
# \ /
|
22
|
-
# n4
|
23
|
-
#
|
24
|
-
# Here are some example queries and outputs:
|
25
|
-
#
|
26
|
-
# n1.links_as_parent # => [#<Link ancestor=n1, descendant=n2>, #<Link ancestor=n1, descendant=n3>]
|
27
|
-
# n4.links_as_child # => [#<Link ancestor=n2, descendant=n4>, #<Link ancestor=n3, descendant=n4>]
|
28
|
-
# n1.links_as_ancestor # => [#<Link ancestor=n1, descendant=n2>, #<Link ancestor=n1, descendant=n3>, #<Link ancestor=n1, descendant=n4>]
|
29
|
-
# n4.links_as_descendant # => [#<Link ancestor=n2, descendant=n4>, #<Link ancestor=n3, descendant=n4>, #<Link ancestor=n1, descendant=n4>]
|
30
|
-
#
|
31
|
-
# In this example, we used +Link+ as the class for all links. This isn't
|
32
|
-
# actually what you get back (see Dagnabit::Link for details), but the
|
33
|
-
# objects you get back _will_ have ancestor and descendant accessors.
|
34
|
-
#
|
35
|
-
module Associations
|
36
|
-
#
|
37
|
-
# Installs associations on the node model.
|
38
|
-
#
|
39
|
-
def self.extended(base)
|
40
|
-
base.install_associations
|
41
|
-
end
|
42
|
-
|
43
|
-
def install_associations
|
44
|
-
klass = self
|
45
|
-
link_class = klass.link_class_name.constantize
|
46
|
-
|
47
|
-
klass.send(:has_many,
|
48
|
-
:links_as_parent,
|
49
|
-
:class_name => klass.link_class_name,
|
50
|
-
:foreign_key => link_class.ancestor_id_column,
|
51
|
-
:conditions => { link_class.ancestor_type_column => klass.name },
|
52
|
-
:dependent => :destroy)
|
53
|
-
|
54
|
-
klass.send(:has_many,
|
55
|
-
:links_as_child,
|
56
|
-
:class_name => klass.link_class_name,
|
57
|
-
:foreign_key => link_class.descendant_id_column,
|
58
|
-
:conditions => { link_class.descendant_type_column => klass.name },
|
59
|
-
:dependent => :destroy)
|
60
|
-
|
61
|
-
klass.send(:has_many,
|
62
|
-
:links_as_ancestor,
|
63
|
-
:class_name => link_class.transitive_closure_class.name,
|
64
|
-
:foreign_key => link_class.ancestor_id_column,
|
65
|
-
:conditions => { link_class.ancestor_type_column => klass.name },
|
66
|
-
:readonly => true)
|
67
|
-
|
68
|
-
klass.send(:has_many,
|
69
|
-
:links_as_descendant,
|
70
|
-
:class_name => link_class.transitive_closure_class.name,
|
71
|
-
:foreign_key => link_class.descendant_id_column,
|
72
|
-
:conditions => { link_class.descendant_type_column => klass.name },
|
73
|
-
:readonly => true)
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def inherited(subclass)
|
79
|
-
super(subclass)
|
80
|
-
subclass.install_associations
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
|
3
|
-
module Dagnabit
|
4
|
-
module Node
|
5
|
-
module ClassMethods
|
6
|
-
#
|
7
|
-
# Returns a subgraph rooted at a given set of nodes.
|
8
|
-
#
|
9
|
-
# While you can retrieve all descendants of a node pretty easily (i.e.
|
10
|
-
# +node.descendants+), and all links from a node really easily (i.e.
|
11
|
-
# +node.links_as_ancestor), it's not quite as straightforward to get the
|
12
|
-
# nodes and edges (direct edges, that is) out of a graph.
|
13
|
-
#
|
14
|
-
# The subgraph is returned as an object with two properties: +nodes+ and
|
15
|
-
# +edges+. Both properties are instances of the Set class.
|
16
|
-
#
|
17
|
-
# == Examples
|
18
|
-
#
|
19
|
-
# In the following examples, the node class is called +Node+. Variables
|
20
|
-
# of the form +nM+, where M is an integer, denote Node instances.
|
21
|
-
#
|
22
|
-
# Retrieve all descendants of n1 and all direct edges "underneath" n1
|
23
|
-
# (i.e. n1 to its children and direct edges for each of n1's
|
24
|
-
# descendants):
|
25
|
-
#
|
26
|
-
# <pre>
|
27
|
-
# Node.subgraph_from(n1)
|
28
|
-
#
|
29
|
-
# => #<Graph nodes=... edges=...>
|
30
|
-
# </pre>
|
31
|
-
#
|
32
|
-
# Same as above, but builds a graph using n1 and n2 as roots.
|
33
|
-
#
|
34
|
-
# <pre>
|
35
|
-
# Node.subgraph_from(n1, n2)
|
36
|
-
# </pre>
|
37
|
-
#
|
38
|
-
# == Usage tip
|
39
|
-
#
|
40
|
-
# +subgraph_from+ forces loading of the +descendants+ and
|
41
|
-
# +links_as_parent+ +links_as_parent+ associations on Node. In some
|
42
|
-
# situations, you may experience better performance if you use
|
43
|
-
# ActiveRecord's eager-loading capabilities when using subgraph_from:
|
44
|
-
#
|
45
|
-
# <pre>
|
46
|
-
# roots = Node.find(..., :include => [:descendants, :links_as_parent])
|
47
|
-
# Node.subgraph_from(roots)
|
48
|
-
# </pre>
|
49
|
-
#
|
50
|
-
def subgraph_from(*roots)
|
51
|
-
OpenStruct.new.tap do |g|
|
52
|
-
g.nodes = all_nodes_of(roots)
|
53
|
-
g.edges = direct_edges_of(roots)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def all_nodes_of(roots)
|
60
|
-
roots.inject(Set.new) do |r, root|
|
61
|
-
r += root.descendants
|
62
|
-
r << root
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def direct_edges_of(roots)
|
67
|
-
roots.inject(Set.new) do |r, root|
|
68
|
-
r += root.links_as_ancestor.find(:all, :include => { :descendant => :links_as_parent }).map { |l| l.descendant.links_as_parent }.flatten
|
69
|
-
r += root.links_as_parent
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Dagnabit
|
2
|
-
module Node
|
3
|
-
#
|
4
|
-
# Configuration mechanism for nodes in a dagnabit-managed dag.
|
5
|
-
#
|
6
|
-
module Configuration
|
7
|
-
#
|
8
|
-
# Writes accessors for configuration data into a node.
|
9
|
-
#
|
10
|
-
# The following accessors are available:
|
11
|
-
# [link_class_name]
|
12
|
-
# The name of the model used to link nodes of this class.
|
13
|
-
#
|
14
|
-
def self.extended(base)
|
15
|
-
base.class_inheritable_accessor :link_class_name
|
16
|
-
end
|
17
|
-
|
18
|
-
#
|
19
|
-
# Configure node behavior.
|
20
|
-
#
|
21
|
-
def configure_acts_as_dag_node(link_class_name)
|
22
|
-
self.link_class_name = link_class_name
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
module Dagnabit
|
2
|
-
module Node
|
3
|
-
#
|
4
|
-
# Instance methods for finding out the neighbors of a node.
|
5
|
-
#
|
6
|
-
# These methods do _not_ behave like association proxies: they're just
|
7
|
-
# wrappers around <tt>ActiveRecord::Base#find</tt>. Therefore, they do not
|
8
|
-
# cache, do not support calculations, do not support extension modules,
|
9
|
-
# named scopes, etc.
|
10
|
-
#
|
11
|
-
# These methods aren't association proxies because a link's ancestor and
|
12
|
-
# descendant are polymorphic associations, and ActiveRecord does not
|
13
|
-
# support polymorphic has_many :through associations.
|
14
|
-
#
|
15
|
-
module Neighbors
|
16
|
-
#
|
17
|
-
# Finds the parents (immediate predecessors) of this node.
|
18
|
-
#
|
19
|
-
def parents
|
20
|
-
links_as_child.find(:all, :include => :ancestor).map { |l| l.ancestor }
|
21
|
-
end
|
22
|
-
|
23
|
-
#
|
24
|
-
# Finds the parents (immediate predecessors) of this node satisfying a given type.
|
25
|
-
#
|
26
|
-
def parents_of_type(type)
|
27
|
-
links_as_child.ancestor_type(type).find(:all, :include => :ancestor).map { |l| l.ancestor }
|
28
|
-
end
|
29
|
-
|
30
|
-
#
|
31
|
-
# Finds the children (immediate successors) of this node.
|
32
|
-
#
|
33
|
-
def children
|
34
|
-
links_as_parent.find(:all, :include => :descendant).map { |l| l.descendant }
|
35
|
-
end
|
36
|
-
|
37
|
-
#
|
38
|
-
# Finds the children (immediate successors) of this node satisfying a given type.
|
39
|
-
#
|
40
|
-
def children_of_type(type)
|
41
|
-
links_as_parent.descendant_type(type).find(:all, :include => :descendant).map { |l| l.descendant }
|
42
|
-
end
|
43
|
-
|
44
|
-
#
|
45
|
-
# Find the ancestors (predecessors) of this node.
|
46
|
-
#
|
47
|
-
def ancestors
|
48
|
-
links_as_descendant.find(:all, :include => :ancestor).map { |l| l.ancestor }
|
49
|
-
end
|
50
|
-
|
51
|
-
#
|
52
|
-
# Find the ancestors (predecessors) of this node satisfying a given type.
|
53
|
-
#
|
54
|
-
def ancestors_of_type(type)
|
55
|
-
links_as_descendant.ancestor_type(type).find(:all, :include => :ancestor).map { |l| l.ancestor }
|
56
|
-
end
|
57
|
-
|
58
|
-
#
|
59
|
-
# Finds the descendants (successors) of this node.
|
60
|
-
#
|
61
|
-
def descendants
|
62
|
-
links_as_ancestor.find(:all, :include => :descendant).map { |l| l.descendant }
|
63
|
-
end
|
64
|
-
|
65
|
-
#
|
66
|
-
# Finds the descendants (successors) of this node satisfying a given type.
|
67
|
-
#
|
68
|
-
def descendants_of_type(type)
|
69
|
-
links_as_ancestor.descendant_type(type).find(:all, :include => :descendant).map { |l| l.descendant }
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|