acts_as_happy_tree 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
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:
|