acts_as_happy_tree 1.0.0 → 1.0.1
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/acts_as_happy_tree.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{acts_as_happy_tree}
|
8
|
-
s.version = "1.0.
|
8
|
+
s.version = "1.0.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["David Heinemeier Hansson", "Jim Gay", "thoughtafter", "and others"]
|
@@ -22,11 +22,15 @@ Gem::Specification.new do |s|
|
|
22
22
|
"acts_as_happy_tree.gemspec",
|
23
23
|
"init.rb",
|
24
24
|
"rails/init.rb",
|
25
|
-
"test/acts_as_happy_tree_test.rb"
|
25
|
+
"test/acts_as_happy_tree_test.rb",
|
26
|
+
"lib/active_record/acts/happy_tree.rb",
|
27
|
+
"lib/active_record/acts/breadth_first.rb",
|
28
|
+
"lib/active_record/acts/depth_first.rb",
|
29
|
+
"lib/acts_as_happy_tree.rb",
|
26
30
|
]
|
27
31
|
s.homepage = %q{http://github.com/thoughtafter/acts_as_happy_tree}
|
28
32
|
s.require_paths = ["lib"]
|
29
|
-
s.rubygems_version = %q{1.0.
|
33
|
+
s.rubygems_version = %q{1.0.1}
|
30
34
|
s.summary = %q{acts_as_happy_tree gem}
|
31
35
|
|
32
36
|
if s.respond_to? :specification_version then
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Acts
|
3
|
+
module HappyTree
|
4
|
+
module BreadthFirst
|
5
|
+
|
6
|
+
def each_level_ids(options={})
|
7
|
+
level_ids = [id]
|
8
|
+
until level_ids.empty?
|
9
|
+
level_ids = self.class.child_ids_of(level_ids, options)
|
10
|
+
yield level_ids
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def each_level_nodes(options={})
|
15
|
+
level_nodes = [self]
|
16
|
+
until level_nodes.empty?
|
17
|
+
ids = level_nodes.map(&:id)
|
18
|
+
level_nodes = self.class.children_of(ids, options)
|
19
|
+
yield level_nodes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns a flat list of the descendants of the current node using a
|
24
|
+
# breadth-first search http://en.wikipedia.org/wiki/Breadth-first_search
|
25
|
+
#
|
26
|
+
# root.descendants_bfs # => [child1, child2, subchild1, subchild2]
|
27
|
+
# options can be passed such as:
|
28
|
+
# select - only return specified attributes, must include id
|
29
|
+
# conditions - only return matching objects, will not return children
|
30
|
+
# of any unmatched objects
|
31
|
+
# order - will set the order of each set of children
|
32
|
+
# limit - will limit max number of children for each parent
|
33
|
+
#
|
34
|
+
# number of DB calls == number of levels
|
35
|
+
# for the example there will be 3 DB calls
|
36
|
+
def descendants_bfs(options={})
|
37
|
+
desc_nodes = []
|
38
|
+
each_level_nodes(options) do |nodes|
|
39
|
+
desc_nodes += nodes
|
40
|
+
end
|
41
|
+
return desc_nodes
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a flat list of the descendant ids of the current node using a
|
45
|
+
# breadth-first search http://en.wikipedia.org/wiki/Breadth-first_search
|
46
|
+
# DB calls = level of tree
|
47
|
+
# only id field returned in query
|
48
|
+
def descendant_ids_bfs(options={})
|
49
|
+
desc_ids = []
|
50
|
+
each_level_ids(options) do |level_ids|
|
51
|
+
desc_ids += level_ids
|
52
|
+
end
|
53
|
+
return desc_ids
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the number of descendants
|
57
|
+
# DB calls = level of tree
|
58
|
+
# only id field selected in query
|
59
|
+
def descendants_count_bfs(options={})
|
60
|
+
count = 0
|
61
|
+
each_level_ids(options) do |level_ids|
|
62
|
+
count += level_ids.count
|
63
|
+
end
|
64
|
+
return count
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return all descendants and current node, this is present for
|
68
|
+
# completeness with other tree implementations
|
69
|
+
def self_and_descendants_bfs(options={})
|
70
|
+
[self] + descendants_bfs(options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def descendant_ids_bfs_rec(options={})
|
74
|
+
descendants_bfs_rec(options.merge(:select=>'id')).map(&:id)
|
75
|
+
end
|
76
|
+
|
77
|
+
def descendants_bfs_rec(options={})
|
78
|
+
c = children.all(options)
|
79
|
+
c + c.map do |child|
|
80
|
+
child.descendants_bfs_rec(options)
|
81
|
+
end.flatten
|
82
|
+
end
|
83
|
+
|
84
|
+
def descendants_count_bfs_rec(options={})
|
85
|
+
children.count + children.all(options.merge(:select=>'id')).reduce(0) do |count, child|
|
86
|
+
count += child.descendants_count_bfs_rec(options)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Acts
|
3
|
+
module HappyTree
|
4
|
+
module DepthFirst
|
5
|
+
|
6
|
+
def each_descendant_id(options={})
|
7
|
+
node_ids = self.class.child_ids_of(id, options)
|
8
|
+
until node_ids.empty?
|
9
|
+
node_id = node_ids.shift
|
10
|
+
yield node_id
|
11
|
+
node_ids.unshift(*self.class.child_ids_of(node_id, options))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def each_descendant_node(options={})
|
16
|
+
nodes = self.class.children_of(id, options)
|
17
|
+
until nodes.empty?
|
18
|
+
node = nodes.shift
|
19
|
+
yield node
|
20
|
+
nodes.unshift(*self.class.children_of(node.id, options))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns a flat list of the descendants of the current node using a
|
25
|
+
# depth-first search http://en.wikipedia.org/wiki/Depth-first_search
|
26
|
+
#
|
27
|
+
# root.descendants_dfs # => [child1, subchild1, subchild2, child2]
|
28
|
+
# options can be passed such as:
|
29
|
+
# select - only return specified attributes, must include "parent_id"
|
30
|
+
# conditions - only return matching objects, will not return children
|
31
|
+
# of any unmatched objects
|
32
|
+
# order - will set the order of each set of children
|
33
|
+
# limit - will limit max number of children for each parent
|
34
|
+
#
|
35
|
+
# this is a recursive method
|
36
|
+
# the number of DB calls == number of descendants + 1
|
37
|
+
def descendants_dfs(options={})
|
38
|
+
desc_nodes = []
|
39
|
+
each_descendant_node(options) do |node|
|
40
|
+
desc_nodes << node
|
41
|
+
end
|
42
|
+
return desc_nodes
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a flat list of the descendant ids of the current node using a
|
46
|
+
# depth-first search http://en.wikipedia.org/wiki/Depth-first_search
|
47
|
+
# DB calls = number of descendants + 1
|
48
|
+
# only id field returned in query
|
49
|
+
def descendant_ids_dfs(options={})
|
50
|
+
desc_ids = []
|
51
|
+
each_descendant_id(options) do |id|
|
52
|
+
desc_ids << id
|
53
|
+
end
|
54
|
+
return desc_ids
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return the number of descendants
|
58
|
+
# DB calls = # of descendants + 1
|
59
|
+
# only id field selected in query
|
60
|
+
def descendants_count_dfs(options={})
|
61
|
+
count = 0
|
62
|
+
each_descendant_id(options) do |id|
|
63
|
+
count += 1
|
64
|
+
end
|
65
|
+
return count
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return all descendants and current node, this is present for
|
69
|
+
# completeness with other tree implementations
|
70
|
+
def self_and_descendants_dfs(options={})
|
71
|
+
[self] + descendants_dfs(options)
|
72
|
+
end
|
73
|
+
|
74
|
+
def descendant_ids_dfs_rec(options={})
|
75
|
+
descendants_dfs_rec(options.merge(:select=>'id')).map(&:id)
|
76
|
+
end
|
77
|
+
|
78
|
+
def descendants_dfs_rec(options={})
|
79
|
+
children.all(options).map do |child|
|
80
|
+
[child] + child.descendants_dfs_rec(options)
|
81
|
+
end.flatten
|
82
|
+
end
|
83
|
+
|
84
|
+
def descendants_count_dfs_rec(options={})
|
85
|
+
children.all(options.merge(:select=>'id')).reduce(0) do |count, child|
|
86
|
+
count += 1 + child.descendants_count_dfs_rec(options)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,381 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Acts
|
3
|
+
module HappyTree
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
|
9
|
+
# association. This requires that you have a foreign key column, which by default is called +parent_id+.
|
10
|
+
#
|
11
|
+
# class Category < ActiveRecord::Base
|
12
|
+
# acts_as_happy_tree :order => "name"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
# root
|
17
|
+
# \_ child1
|
18
|
+
# \_ subchild1
|
19
|
+
# \_ subchild2
|
20
|
+
#
|
21
|
+
# root = Category.create("name" => "root")
|
22
|
+
# child1 = root.children.create("name" => "child1")
|
23
|
+
# subchild1 = child1.children.create("name" => "subchild1")
|
24
|
+
#
|
25
|
+
# root.parent # => nil
|
26
|
+
# child1.parent # => root
|
27
|
+
# root.children # => [child1]
|
28
|
+
# root.children.first.children.first # => subchild1
|
29
|
+
#
|
30
|
+
# In addition to the parent and children associations, the following instance methods are added to the class
|
31
|
+
# after calling <tt>acts_as_happy_tree</tt>:
|
32
|
+
# * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node (<tt>[subchild2]</tt> when called on <tt>subchild1</tt>)
|
33
|
+
# * <tt>self_and_siblings</tt> - Returns all the children of the parent, including the current node (<tt>[subchild1, subchild2]</tt> when called on <tt>subchild1</tt>)
|
34
|
+
# * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[child1, root]</tt> when called on <tt>subchild2</tt>)
|
35
|
+
# * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>subchild2</tt>)
|
36
|
+
# * <tt>descendants</tt> - Returns a flat list of the descendants of the current node (<tt>[child1, subchild1, subchild2]</tt> when called on <tt>root</tt>)
|
37
|
+
module ClassMethods
|
38
|
+
# Configuration options are:
|
39
|
+
#
|
40
|
+
# * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
|
41
|
+
# * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
|
42
|
+
# * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
|
43
|
+
def acts_as_happy_tree(options = {})
|
44
|
+
configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil, :dependent => :destroy, :touch => false }
|
45
|
+
configuration.update(options) if options.is_a?(Hash)
|
46
|
+
|
47
|
+
belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache], :touch => configuration[:touch]
|
48
|
+
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent]
|
49
|
+
|
50
|
+
validate :parent_key_must_be_valid
|
51
|
+
|
52
|
+
class_eval <<-EOV
|
53
|
+
include ActiveRecord::Acts::HappyTree::InstanceMethods
|
54
|
+
include ActiveRecord::Acts::HappyTree::BreadthFirst
|
55
|
+
include ActiveRecord::Acts::HappyTree::DepthFirst
|
56
|
+
|
57
|
+
scope :roots, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}
|
58
|
+
|
59
|
+
after_update :update_parents_counter_cache
|
60
|
+
|
61
|
+
def self.root
|
62
|
+
roots.first
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.childless
|
66
|
+
nodes = []
|
67
|
+
|
68
|
+
find(:all).each do |node|
|
69
|
+
nodes << node if node.children.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
nodes
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.parent_key
|
76
|
+
"#{configuration[:foreign_key]}"
|
77
|
+
end
|
78
|
+
EOV
|
79
|
+
end
|
80
|
+
|
81
|
+
# AR >= 3.2 only
|
82
|
+
def pluck_parent_id_of(node_id)
|
83
|
+
where(:id=>node_id).pluck(parent_key).first
|
84
|
+
end
|
85
|
+
|
86
|
+
def pluck_child_ids_of(node_ids, options={})
|
87
|
+
where(parent_key=>node_ids).apply_finder_options(options).pluck(:id)
|
88
|
+
end
|
89
|
+
|
90
|
+
# AR >= 3.0 only
|
91
|
+
def select_parent_id_of(node_id)
|
92
|
+
where(:id=>node_id).select(parent_key).first[parent_key]
|
93
|
+
end
|
94
|
+
|
95
|
+
def select_child_ids_of(node_ids, options={})
|
96
|
+
where(parent_key=>node_ids).apply_finder_options(options.merge(:select=>:id)).map(&:id)
|
97
|
+
end
|
98
|
+
|
99
|
+
if ActiveRecord::Base.respond_to?(:pluck)
|
100
|
+
alias :parent_id_of :pluck_parent_id_of
|
101
|
+
alias :child_ids_of :pluck_child_ids_of
|
102
|
+
else
|
103
|
+
alias :parent_id_of :select_parent_id_of
|
104
|
+
alias :child_ids_of :select_child_ids_of
|
105
|
+
end
|
106
|
+
|
107
|
+
def children_of(node_ids, options={})
|
108
|
+
where(parent_key=>node_ids).apply_finder_options(options)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
module InstanceMethods
|
114
|
+
# Returns list of ancestors, starting from parent until root.
|
115
|
+
#
|
116
|
+
# subchild1.ancestors # => [child1, root]
|
117
|
+
def ancestors
|
118
|
+
node, nodes = self, []
|
119
|
+
nodes << node = node.parent until node.parent.nil? and return nodes
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns the root node of the tree.
|
123
|
+
def root_classic
|
124
|
+
node = self
|
125
|
+
node = node.parent until node.parent.nil? and return node
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns all siblings of the current node.
|
129
|
+
#
|
130
|
+
# subchild1.siblings # => [subchild2]
|
131
|
+
def siblings
|
132
|
+
self_and_siblings - [self]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns all siblings and a reference to the current node.
|
136
|
+
#
|
137
|
+
# subchild1.self_and_siblings # => [subchild1, subchild2]
|
138
|
+
def self_and_siblings
|
139
|
+
parent ? parent.children : self.class.roots
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a flat list of the descendants of the current node.
|
143
|
+
#
|
144
|
+
# root.descendants # => [child1, subchild1, subchild2]
|
145
|
+
def descendants_classic(node=self)
|
146
|
+
nodes = []
|
147
|
+
nodes << node unless node == self
|
148
|
+
|
149
|
+
node.children.each do |child|
|
150
|
+
nodes += descendants_classic(child)
|
151
|
+
end
|
152
|
+
|
153
|
+
nodes.compact
|
154
|
+
end
|
155
|
+
|
156
|
+
def childless
|
157
|
+
self.descendants.collect{|d| d.children.empty? ? d : nil}.compact
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns true if this instance has no parent (aka root node)
|
161
|
+
#
|
162
|
+
# root.root? # => true
|
163
|
+
# child1.root? # => false
|
164
|
+
#
|
165
|
+
# no DB access
|
166
|
+
def root?
|
167
|
+
tree_parent_key.nil?
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns true if this instance has a parent (aka child node)
|
171
|
+
#
|
172
|
+
# root.child? # => false
|
173
|
+
# child1.child? # => true
|
174
|
+
#
|
175
|
+
# no DB access
|
176
|
+
def child?
|
177
|
+
!tree_parent_key.nil?
|
178
|
+
end
|
179
|
+
|
180
|
+
# returns true if this instance has any children (aka parent node)
|
181
|
+
#
|
182
|
+
# root.parent? # => true
|
183
|
+
# subchild1.parent? # => false
|
184
|
+
#
|
185
|
+
# 1 DB SELECT, no fields selected
|
186
|
+
def parent?
|
187
|
+
children.exists?
|
188
|
+
end
|
189
|
+
|
190
|
+
# returns true if this instance has no children (aka leaf node)
|
191
|
+
#
|
192
|
+
# root.leaf? # => false
|
193
|
+
# subchild1.leaf? # => true
|
194
|
+
#
|
195
|
+
# 1 DB SELECT, no fields selected
|
196
|
+
def leaf?
|
197
|
+
!children.exists?
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns true if this instance is an ancestor of another instance
|
201
|
+
#
|
202
|
+
# root.ancestor_of?(child1) # => true
|
203
|
+
# child1.ancestor_of?(root) # => false
|
204
|
+
#
|
205
|
+
# 1 DB SELECT per level examined, only selects "parent_id"
|
206
|
+
# AR < 3.2 = 1 AR object per level examined
|
207
|
+
# AR >= 3.2 = no AR objects
|
208
|
+
#
|
209
|
+
# equivalent of node.descendant_of?(self)
|
210
|
+
# node1.ancestor_of(node2) == node2.descendant_of(node1)
|
211
|
+
def ancestor_of?(node)
|
212
|
+
return false if (node.nil? || !node.is_a?(self.class))
|
213
|
+
key = node.tree_parent_key
|
214
|
+
until key.nil? do
|
215
|
+
return true if key == self.id
|
216
|
+
key = self.class.parent_id_of(key)
|
217
|
+
end
|
218
|
+
return false
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns true if this instance is a descendant of another instance
|
222
|
+
#
|
223
|
+
# root.descendant_of?(child1) # => false
|
224
|
+
# child1.descendant_of?(root) # => true
|
225
|
+
#
|
226
|
+
# same performance as ancestor_of?
|
227
|
+
#
|
228
|
+
# equivalent of node.ancestor_of?(self)
|
229
|
+
# node1.descendant_of(node2) == node2.ancestor_of(node1)
|
230
|
+
def descendant_of?(node)
|
231
|
+
return false if (node.nil? || !node.is_a?(self.class))
|
232
|
+
key = self.tree_parent_key
|
233
|
+
until key.nil? do
|
234
|
+
return true if key == node.id
|
235
|
+
key = self.class.parent_id_of(key)
|
236
|
+
end
|
237
|
+
return false
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns list of ancestor ids, starting from parent until root.
|
241
|
+
#
|
242
|
+
# subchild1.ancestor_ids # => [child1.id, root.id]
|
243
|
+
#
|
244
|
+
# 1 DB SELECT per ancestor, only selects "parent_id"
|
245
|
+
# AR < 3.2 = 1 AR object per ancestor
|
246
|
+
# AR >= 3.2 = 0 AR objects
|
247
|
+
def ancestor_ids
|
248
|
+
key, node_ids = tree_parent_key, []
|
249
|
+
until key.nil? do
|
250
|
+
node_ids << key
|
251
|
+
key = self.class.parent_id_of(key)
|
252
|
+
end
|
253
|
+
return node_ids
|
254
|
+
end
|
255
|
+
|
256
|
+
# Returns a count of the number of ancestors
|
257
|
+
#
|
258
|
+
# subchild1.ancestors_count # => 2
|
259
|
+
#
|
260
|
+
# 1 DB SELECT per ancestor, only selects "parent_id"
|
261
|
+
# AR < 3.2 = 1 AR object per ancestor
|
262
|
+
# AR >= 3.2 = 0 AR objects
|
263
|
+
def ancestors_count
|
264
|
+
key, count = tree_parent_key, 0
|
265
|
+
until key.nil? do
|
266
|
+
count += 1
|
267
|
+
key = self.class.parent_id_of(key)
|
268
|
+
end
|
269
|
+
return count
|
270
|
+
end
|
271
|
+
|
272
|
+
# Returns the root id of the current node
|
273
|
+
# no DB access if node is root
|
274
|
+
# otherwise use root_id function which is optimized
|
275
|
+
# 1 DB SELECT per ancestor, only selects "parent_id"
|
276
|
+
# AR < 3.2 = 1 AR object per ancestor
|
277
|
+
# AR >= 3.2 = 0 AR objects
|
278
|
+
def root_id
|
279
|
+
key, node_id = tree_parent_key, id
|
280
|
+
until key.nil? do
|
281
|
+
node_id = key
|
282
|
+
key = self.class.parent_id_of(key)
|
283
|
+
end
|
284
|
+
return node_id
|
285
|
+
end
|
286
|
+
|
287
|
+
# Returns the root node of the current node
|
288
|
+
# no DB access if node is root
|
289
|
+
# otherwise use root_id function which is optimized
|
290
|
+
# performance = root_id + 1 DB SELECT and 1 AR object if not root
|
291
|
+
# other acts_as_tree variants select all fields and instantiate all objects
|
292
|
+
def root
|
293
|
+
root? ? self : self.class.find(root_id)
|
294
|
+
end
|
295
|
+
|
296
|
+
# helper method to allow the choosing of the descendants traversal
|
297
|
+
# method by setting :traversal option as follows:
|
298
|
+
#
|
299
|
+
# :classic - depth-first search, recursive
|
300
|
+
# - only for descendants, ignores finder options
|
301
|
+
# :dfs - depth-first search, iterative
|
302
|
+
# :dfs_rec - depth-first search, recursive
|
303
|
+
# :bfs - breadth-first search, interative
|
304
|
+
# :bfs_rec - breadth-first search, recursive
|
305
|
+
def descendants_call(method, default, options={})
|
306
|
+
traversal = options.delete(:traversal)
|
307
|
+
case traversal
|
308
|
+
when :classic
|
309
|
+
send("#{method}_classic")
|
310
|
+
when :bfs, :dfs, :bfs_rec, :dfs_rec
|
311
|
+
send("#{method}_#{traversal}", options)
|
312
|
+
else
|
313
|
+
send("#{method}_#{default}", options)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# returns all of the descendants
|
318
|
+
# uses iterative method in DFS order by default
|
319
|
+
# options are finder options
|
320
|
+
def descendants(options={})
|
321
|
+
descendants_call(:descendants, :dfs, options)
|
322
|
+
end
|
323
|
+
|
324
|
+
# return self and descendants
|
325
|
+
# provided for compatibility with other tree implementations
|
326
|
+
# uses iterative method in DFS order by default
|
327
|
+
# options are finder options
|
328
|
+
def self_and_descendants(options={})
|
329
|
+
descendants_call(:self_and_descendants, :dfs, options)
|
330
|
+
end
|
331
|
+
|
332
|
+
# return an array of descendant ids
|
333
|
+
# uses iterative method in DFS order by default
|
334
|
+
# options are finder options
|
335
|
+
def descendant_ids(options={})
|
336
|
+
descendants_call(:descendant_ids, :dfs, options)
|
337
|
+
end
|
338
|
+
|
339
|
+
# return a count of the number of descendants
|
340
|
+
# Use BFS for descendants_count because it should use fewer SQL queries
|
341
|
+
# and thus be faster
|
342
|
+
# options are finder options
|
343
|
+
def descendants_count(options={})
|
344
|
+
descendants_call(:descendants_count, :bfs, options)
|
345
|
+
end
|
346
|
+
|
347
|
+
# method for validating parent_key to make sure it is not
|
348
|
+
# 1) the same as id
|
349
|
+
# 2) already a descendant of the current node
|
350
|
+
def parent_key_must_be_valid
|
351
|
+
return if id.nil?
|
352
|
+
if (tree_parent_key==id)
|
353
|
+
errors.add(tree_parent_key_name, "#{tree_parent_key_name} cannot be the same as id")
|
354
|
+
elsif ancestor_of?(parent)
|
355
|
+
errors.add(tree_parent_key_name, "#{tree_parent_key_name} cannot be a descendant")
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
def update_parents_counter_cache
|
362
|
+
if self.respond_to?(:children_count) && parent_id_changed?
|
363
|
+
self.class.decrement_counter(:children_count, parent_id_was)
|
364
|
+
self.class.increment_counter(:children_count, parent_id)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
protected
|
369
|
+
|
370
|
+
def tree_parent_key_name
|
371
|
+
self.class.parent_key
|
372
|
+
end
|
373
|
+
|
374
|
+
def tree_parent_key
|
375
|
+
attributes[tree_parent_key_name]
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_happy_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -29,6 +29,10 @@ files:
|
|
29
29
|
- init.rb
|
30
30
|
- rails/init.rb
|
31
31
|
- test/acts_as_happy_tree_test.rb
|
32
|
+
- lib/active_record/acts/happy_tree.rb
|
33
|
+
- lib/active_record/acts/breadth_first.rb
|
34
|
+
- lib/active_record/acts/depth_first.rb
|
35
|
+
- lib/acts_as_happy_tree.rb
|
32
36
|
homepage: http://github.com/thoughtafter/acts_as_happy_tree
|
33
37
|
licenses: []
|
34
38
|
post_install_message:
|