1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
# app/models/concerns/nested_tree.rb
#
# Minimal parent_id-based replacement for the tree-traversal subset of
# awesome_nested_set actually used by this app (descendants, ancestors,
# level, root, move_to_child_of)
module NestedTree
extend ActiveSupport::Concern
included do
belongs_to :parent, class_name: name, foreign_key: :parent_id, optional: true, inverse_of: :children
has_many :children, -> { order(:id) }, class_name: name, foreign_key: :parent_id, inverse_of: :parent
before_destroy :delete_descendants
end
class_methods do
def root
roots.first
end
def roots
where(parent_id: nil)
end
def valid?
# Every non-root row's parent_id must point at a real, existing row.
where.not(parent_id: nil).where.missing(:parent).none?
end
end
def root?
parent_id.nil?
end
def ancestors
result = []
current = parent
while current
result << current
current = current.parent
end
result
end
def level
ancestors.size
end
def descendants
ids = []
queue = self.class.where(parent_id: id).pluck(:id)
until queue.empty?
ids.concat(queue)
queue = self.class.where(parent_id: queue).pluck(:id)
end
self.class.where(id: ids)
end
def self_and_descendants
self.class.where(id: [id] + descendants.pluck(:id))
end
def self_and_descendants_ordered_with_level
nodes = [self] + descendants.to_a
children_by_parent = nodes.group_by(&:parent_id)
children_by_parent.each_value { |list| list.sort_by!(&:id) }
result = []
visit = ->(node, level) do
result << [node, level]
(children_by_parent[node.id] || []).each { |child| visit.call(child, level + 1) }
end
visit.call(self, 0)
result
end
def move_to_child_of(new_parent)
update!(parent_id: new_parent.id)
end
private
def delete_descendants
self.class.where(id: descendants.pluck(:id)).delete_all
end
end
|