fertile_forest 0.0.0 → 1.0.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.
- checksums.yaml +4 -4
- data/LICENSE +22 -0
- data/README.rdoc +3 -0
- data/config/routes.rb +2 -0
- data/lib/fertile_forest/engine.rb +13 -0
- data/lib/fertile_forest/modules/calculators.rb +231 -0
- data/lib/fertile_forest/modules/configs.rb +96 -0
- data/lib/fertile_forest/modules/entities.rb +233 -0
- data/lib/fertile_forest/modules/finders.rb +736 -0
- data/lib/fertile_forest/modules/reconstructers.rb +843 -0
- data/lib/fertile_forest/modules/states.rb +271 -0
- data/lib/fertile_forest/modules/utilities.rb +188 -0
- data/lib/fertile_forest/saplings.rb +330 -0
- data/lib/fertile_forest/version.rb +1 -1
- data/lib/fertile_forest.rb +7 -3
- data/lib/tasks/fertile_forest_tasks.rake +4 -0
- metadata +19 -6
@@ -0,0 +1,736 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
#
|
3
|
+
# Finder methods
|
4
|
+
# Fertile Forest for Ruby: The new model for storing hierarchical data in a database.
|
5
|
+
#
|
6
|
+
# @author StewEucen
|
7
|
+
# @copyright Copyright (c) 2015 Stew Eucen (http://lab.kochlein.com)
|
8
|
+
# @license http://www.opensource.org/licenses/mit-license.php MIT License
|
9
|
+
#
|
10
|
+
# @link http://lab.kochlein.com/FertileForest
|
11
|
+
# @since File available since Release 1.0.0
|
12
|
+
# @version 1.0.0
|
13
|
+
#
|
14
|
+
module StewEucen
|
15
|
+
# Name space of Stew Eucen's Acts
|
16
|
+
module Acts
|
17
|
+
# Name space of Fertile Forest
|
18
|
+
module FertileForest
|
19
|
+
# Name space of class methods for Fertile Forest
|
20
|
+
module Table
|
21
|
+
# This module is for extending into derived class by ActiveRecord.<br>
|
22
|
+
# The caption contains "Instance Methods",
|
23
|
+
# but it means "Class Methods" of each derived class.
|
24
|
+
module Finders
|
25
|
+
#
|
26
|
+
# Find trunk (= ancestor) nodes from base node in ordered range.
|
27
|
+
#
|
28
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
29
|
+
# @param range [Integer] Ordered range of trunk nodes.
|
30
|
+
# -1:To designate as root node.
|
31
|
+
# @param columns [Array] Columns for SELECT clause.
|
32
|
+
# @return [ActiveRecord::Relation] Basic query for finding trunk nodes.
|
33
|
+
# @return [nil] No trunk nodes.
|
34
|
+
#
|
35
|
+
def trunk(base_obj, range = ANCESTOR_ALL, columns = nil)
|
36
|
+
base_node = ff_resolve_nodes(base_obj)
|
37
|
+
return nil if base_node.blank?
|
38
|
+
|
39
|
+
aim_queue = base_node.ff_queue
|
40
|
+
aim_depth = base_node.ff_depth
|
41
|
+
aim_grove = base_node.ff_grove # When no grove, nil
|
42
|
+
|
43
|
+
return nil if aim_depth == ROOT_DEPTH
|
44
|
+
|
45
|
+
ffqq = arel_table[@_ff_queue]
|
46
|
+
ffdd = arel_table[@_ff_depth]
|
47
|
+
ffgg = arel_table[@_ff_grove]
|
48
|
+
|
49
|
+
# create subquery to find queues of ancestor
|
50
|
+
aim_subquery = ff_usual_projection(aim_grove)
|
51
|
+
.where(ffqq.lt(aim_queue))
|
52
|
+
|
53
|
+
if range < 0
|
54
|
+
aim_subquery = aim_subquery.where(ffdd.eq(ROOT_DEPTH))
|
55
|
+
else
|
56
|
+
aim_subquery = aim_subquery.where(ffdd.lt(aim_depth))
|
57
|
+
aim_subquery = aim_subquery.where(ffdd.gteq(aim_depth - range)) \
|
58
|
+
if 0 < range
|
59
|
+
end
|
60
|
+
|
61
|
+
aim_group = [ffdd]
|
62
|
+
aim_group.unshift(ffgg) if has_grove?
|
63
|
+
|
64
|
+
aim_subquery = aim_subquery.project(ffqq.maximum.as('ancestor_queue'))
|
65
|
+
.group(aim_group)
|
66
|
+
|
67
|
+
# find nodes by ancestor queues
|
68
|
+
# must use IN(), because trunk() is for general purpose to find ancestors
|
69
|
+
# When one row, can use "=". When plural, can not use "=".
|
70
|
+
# Error: SQLSTATE[21000]: Cardinality violation: 1242 Subquery returns more than 1 row
|
71
|
+
ff_required_columns_scope()
|
72
|
+
.ff_usual_conditions_scope(aim_grove)
|
73
|
+
.ff_usual_order_scope()
|
74
|
+
.where(ffqq.in(aim_subquery))
|
75
|
+
.select(ff_all_optional_columns(columns))
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Find all ancestor nodes from base node (without base node).
|
80
|
+
#
|
81
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
82
|
+
# @param columns [Array] Columns for SELECT clause.
|
83
|
+
# @return [ActiveRecord::Relation] Basic query for finding ancestor nodes.
|
84
|
+
# @return [nil] No ancestor nodes.
|
85
|
+
#
|
86
|
+
def ancestors(base_obj, columns = nil)
|
87
|
+
trunk(base_obj, ANCESTOR_ALL, columns)
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Find genitor (= parent) node from base node.
|
92
|
+
#
|
93
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
94
|
+
# @param columns [Array] Columns for SELECT clause.
|
95
|
+
# @return [Entity] Genitor node.
|
96
|
+
# @return [nil] No genitor node.
|
97
|
+
#
|
98
|
+
def genitor(base_obj, columns = nil)
|
99
|
+
trunk_query = trunk(base_obj, ANCESTOR_ONLY_PARENT, columns)
|
100
|
+
|
101
|
+
return nil if trunk_query.blank?
|
102
|
+
|
103
|
+
trunk_query.first
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# Find root node from base node.
|
108
|
+
#
|
109
|
+
# @param base_obj [Entity|int] Base node|id to find.
|
110
|
+
# @param columns [Array] Columns for SELECT clause.
|
111
|
+
# @return [Entity] Root node. When base node is root, return base node.
|
112
|
+
# @return [Enil] No root node.
|
113
|
+
#
|
114
|
+
def root(base_obj, columns = nil)
|
115
|
+
base_node = ff_resolve_nodes(base_obj)
|
116
|
+
return nil if base_node.blank?
|
117
|
+
|
118
|
+
return base_node if base_node.ff_depth == ROOT_DEPTH
|
119
|
+
|
120
|
+
trunk_query = trunk(base_node, ANCESTOR_ONLY_ROOT, columns)
|
121
|
+
return nil if trunk_query.blank?
|
122
|
+
|
123
|
+
trunk_query.first
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Find grandparent node from base node.
|
128
|
+
#
|
129
|
+
# @param base_obj [Entity|int] Base node|id to find.
|
130
|
+
# @param columns [Array] Columns for SELECT clause.
|
131
|
+
# @return [Entity] Grandparent node.
|
132
|
+
# @return [nil] No grandparent node.
|
133
|
+
#
|
134
|
+
def grandparent(base_obj, columns = nil)
|
135
|
+
base_node = ff_resolve_nodes(base_obj)
|
136
|
+
return nil if base_node.blank?
|
137
|
+
|
138
|
+
grand_number = 2
|
139
|
+
|
140
|
+
grandparent_depth = base_node.ff_depth - grand_number
|
141
|
+
return nil if grandparent_depth < ROOT_DEPTH
|
142
|
+
|
143
|
+
trunk_query = trunk(base_node, grand_number, columns)
|
144
|
+
return nil if trunk_query.blank?
|
145
|
+
|
146
|
+
trunk_query.first
|
147
|
+
end
|
148
|
+
|
149
|
+
######################################################################
|
150
|
+
|
151
|
+
#
|
152
|
+
# Find subtree nodes from base node with ordered range.
|
153
|
+
#
|
154
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
155
|
+
# @param range [Integer] Ordered range of trunk nodes. -1:To designate as root node.
|
156
|
+
# @param withTop [boolean] Include base node in return query.
|
157
|
+
# @param fields [Array] Fields for SELECT clause.
|
158
|
+
# @return [ActiveRecord::Relation] Basic query for finding subtree nodes.
|
159
|
+
# @return [nil] No subtree nodes.
|
160
|
+
#
|
161
|
+
def subtree(base_obj, range = DESCENDANTS_ALL, with_top = true, columns = nil)
|
162
|
+
base_node = ff_resolve_nodes(base_obj)
|
163
|
+
return [] if base_node.blank?
|
164
|
+
|
165
|
+
aim_query = ff_required_columns_scope(columns)
|
166
|
+
.ff_subtree_scope(
|
167
|
+
base_node,
|
168
|
+
with_top,
|
169
|
+
true # use COALESCE() must be false for children().count
|
170
|
+
)
|
171
|
+
|
172
|
+
ffdd = arel_table[@_ff_depth]
|
173
|
+
limited = ff_limited_subtree_depth(
|
174
|
+
base_node.ff_depth,
|
175
|
+
range,
|
176
|
+
aim_query
|
177
|
+
)
|
178
|
+
|
179
|
+
aim_query.where!(ffdd.lteq(limited)) if 0 < limited
|
180
|
+
|
181
|
+
aim_query
|
182
|
+
.select(ff_all_optional_columns(columns))
|
183
|
+
.ff_usual_order_scope()
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# Count each depth for pagination in finding subtree nodes.
|
188
|
+
# @param base_depth [Integer] Depth of base node.
|
189
|
+
# @param range [Integer] Ordered depth offset.
|
190
|
+
# @param subtree_query [Projection] WHERE clause for finding subtree to count.
|
191
|
+
# @return [Integer] Max depth in query to find subtree nodes.
|
192
|
+
#
|
193
|
+
def ff_limited_subtree_depth(base_depth, range, subtree_query)
|
194
|
+
orderd_depth = range == 0 ? 0 : (base_depth + range)
|
195
|
+
|
196
|
+
ffdd = arel_table[@_ff_depth]
|
197
|
+
|
198
|
+
count_query = subtree_query
|
199
|
+
.select(ffdd.count('*').as('depth_count'))
|
200
|
+
.group(ffdd)
|
201
|
+
|
202
|
+
limited = self.ff_options[:subtree_limit_size]
|
203
|
+
|
204
|
+
total_size = 0
|
205
|
+
limited_depth = 0
|
206
|
+
|
207
|
+
count_query.each do |depth_entity|
|
208
|
+
aim_depth = depth_entity.ff_depth
|
209
|
+
aim_count = depth_entity.depth_count
|
210
|
+
|
211
|
+
braek if limited < total_size += aim_count
|
212
|
+
|
213
|
+
limited_depth = aim_depth
|
214
|
+
end
|
215
|
+
|
216
|
+
limited_depth = [limited_depth, base_depth + 1].max
|
217
|
+
|
218
|
+
if orderd_depth == 0
|
219
|
+
limited_depth
|
220
|
+
else
|
221
|
+
[limited_depth, orderd_depth].min
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
#
|
226
|
+
# Find descendant nodes from base node.
|
227
|
+
#
|
228
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
229
|
+
# @param columns [Array] Columns for SELECT clause.
|
230
|
+
# @return [ActiveRecord::Relation] Basic query for finding descendant nodes.
|
231
|
+
# @return [nil] No descendant nodes.
|
232
|
+
#
|
233
|
+
def descendants(base_obj, columns = nil)
|
234
|
+
subtree(base_obj, DESCENDANTS_ALL, false, columns)
|
235
|
+
end
|
236
|
+
|
237
|
+
#
|
238
|
+
# Find child nodes from base node.
|
239
|
+
#
|
240
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
241
|
+
# @param columns [Array] Columns for SELECT clause.
|
242
|
+
# @return [ActiveRecord::Relation] Basic query for finding child nodes.
|
243
|
+
# @return [nil] No child nodes.
|
244
|
+
#
|
245
|
+
def children(base_obj, columns = nil)
|
246
|
+
subtree(base_obj, DESCENDANTS_ONLY_CHILD, false, columns)
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# Find nth-child node from base node.
|
251
|
+
#
|
252
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
253
|
+
# @param nth [Integer] Order in child nodes.
|
254
|
+
# @param columns [Array] Columns for SELECT clause.
|
255
|
+
# @return [Entity] Nth-child node.
|
256
|
+
# @return [nil] No nth-child node.
|
257
|
+
#
|
258
|
+
def nth_child(base_obj, nth = 0, columns = nil)
|
259
|
+
children_query = children(base_obj, columns)
|
260
|
+
|
261
|
+
nth = nth.to_i
|
262
|
+
if nth < 0
|
263
|
+
sibling_count = children_query.all.length
|
264
|
+
nth = sibling_count - 1
|
265
|
+
return nil if nth < 0
|
266
|
+
end
|
267
|
+
|
268
|
+
children_query.offset(nth).first
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# Find grandchild nodes from base node.
|
273
|
+
#
|
274
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
275
|
+
# @param columns [Array] Columns for SELECT clause.
|
276
|
+
# @return [ActiveRecord::Relation] Basic query for finding grandchild nodes.
|
277
|
+
# @return [nil] No grandchild nodes.
|
278
|
+
#
|
279
|
+
def grandchildren(base_obj, columns = nil)
|
280
|
+
base_node = ff_resolve_nodes(base_obj)
|
281
|
+
return [] if base_node.blank?
|
282
|
+
|
283
|
+
grand_number = 2
|
284
|
+
|
285
|
+
# return value
|
286
|
+
subtree(
|
287
|
+
base_node,
|
288
|
+
grand_number,
|
289
|
+
SUBTREE_WITHOUT_TOP_NODE,
|
290
|
+
columns
|
291
|
+
)
|
292
|
+
.where(@_ff_depth => base_node.ff_depth + grand_number)
|
293
|
+
end
|
294
|
+
|
295
|
+
#
|
296
|
+
# Find sibling nodes from base node.
|
297
|
+
#
|
298
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
299
|
+
# @param columns [Array] Columns for SELECT clause.
|
300
|
+
# @return [ActiveRecord::Relation] Basic query for finding sibling nodes.
|
301
|
+
# @return [nil] No sibling nodes.
|
302
|
+
#
|
303
|
+
def siblings(base_obj, columns = nil)
|
304
|
+
parent_node = genitor(base_obj)
|
305
|
+
children(parent_node, columns)
|
306
|
+
end
|
307
|
+
|
308
|
+
#
|
309
|
+
# Find nth-sibling node from base node.
|
310
|
+
#
|
311
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
312
|
+
# @param nth [Integer] Order in child nodes.
|
313
|
+
# @param [Array] columns Columns for SELECT clause.
|
314
|
+
# @return [Entity] Nth-sibling node.
|
315
|
+
# @return [nil] No nth-sibling node.
|
316
|
+
#
|
317
|
+
def nth_sibling(base_obj, nth = 0, columns = nil)
|
318
|
+
base_node = ff_resolve_nodes(base_obj)
|
319
|
+
return nil if base_node.blank?
|
320
|
+
|
321
|
+
parent_node = genitor(base_node)
|
322
|
+
return nil if parent_node.blank?
|
323
|
+
|
324
|
+
nth = nth.to_i
|
325
|
+
nth_child(parent_node, nth, columns)
|
326
|
+
end
|
327
|
+
|
328
|
+
#
|
329
|
+
# Find elder sibling node from base node.
|
330
|
+
#
|
331
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
332
|
+
# @param columns [Array] Columns for SELECT clause.
|
333
|
+
# @return [Entity] Elder sibling node of base node.
|
334
|
+
# @return [nil] No elder sibling node.
|
335
|
+
#
|
336
|
+
def elder_sibling(base_obj, columns = nil)
|
337
|
+
offset_sibling(base_obj, -1, columns)
|
338
|
+
end
|
339
|
+
|
340
|
+
#
|
341
|
+
# Find younger sibling node from base node.
|
342
|
+
#
|
343
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
344
|
+
# @param columns [Array] Columns for SELECT clause.
|
345
|
+
# @return [Entity] Younger sibling node of base node.
|
346
|
+
# @return [nil] No younger sibling node.
|
347
|
+
#
|
348
|
+
def younger_sibling(base_obj, columns = nil)
|
349
|
+
offset_sibling(base_obj, 1, columns)
|
350
|
+
end
|
351
|
+
|
352
|
+
#
|
353
|
+
# Find offsetted sibling node from base node.
|
354
|
+
#
|
355
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
356
|
+
# @param offset [Integer] Order in child nodes.
|
357
|
+
# @param columns [Array] Columns for SELECT clause.
|
358
|
+
# @return [Entity] Offsetted sibling node from base node.
|
359
|
+
# @return [nil] No offsetted sibling node.
|
360
|
+
#
|
361
|
+
def offset_sibling(base_obj, offset, columns = nil)
|
362
|
+
base_node = ff_resolve_nodes(base_obj)
|
363
|
+
return nil if base_node.blank?
|
364
|
+
|
365
|
+
parent_node = genitor(base_node)
|
366
|
+
return nil if parent_node.blank?
|
367
|
+
|
368
|
+
sibling_nodes = children(parent_node, [@_id]).all
|
369
|
+
return nil if sibling_nodes.blank?
|
370
|
+
|
371
|
+
base_id = base_node.id
|
372
|
+
|
373
|
+
nth = nil
|
374
|
+
Array(sibling_nodes).each.with_index do |node, i|
|
375
|
+
if node.id == base_id
|
376
|
+
nth = i
|
377
|
+
break
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
return nil if nth.nil?
|
382
|
+
|
383
|
+
offset = offset.to_i
|
384
|
+
|
385
|
+
# OFFSET -1 make an error
|
386
|
+
# Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax;
|
387
|
+
# check the manual that corresponds to your MySQL server version for the right syntax to use near '-1' at line 1
|
388
|
+
return nil if nth + offset < 0
|
389
|
+
|
390
|
+
nth_child(parent_node, nth + offset, columns)
|
391
|
+
end
|
392
|
+
|
393
|
+
#
|
394
|
+
# Find leaf nodes from base node.
|
395
|
+
#
|
396
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
397
|
+
# @param columns [Array] Columns for SELECT clause.
|
398
|
+
# @return [Array] Basic query for finding leaf nodes.
|
399
|
+
# @return [nil] No leaf nodes.
|
400
|
+
# @todo Pagination, Create limit as desc.
|
401
|
+
#
|
402
|
+
def leaves(base_obj, columns = nil)
|
403
|
+
ff_features(base_obj, false, columns)
|
404
|
+
end
|
405
|
+
|
406
|
+
#
|
407
|
+
# Find internal nodes from base node.
|
408
|
+
#
|
409
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
410
|
+
# @param columns [Array] Columns for SELECT clause.
|
411
|
+
# @return [Array] Basic query for finding internal nodes.
|
412
|
+
# @return [nil] No internal nodes.
|
413
|
+
#
|
414
|
+
def internals(base_obj, columns = nil)
|
415
|
+
ff_features(base_obj, true, columns)
|
416
|
+
end
|
417
|
+
|
418
|
+
#
|
419
|
+
# Find feature nodes in subtree from base node.
|
420
|
+
# feature nodes
|
421
|
+
# (1) leaves
|
422
|
+
# (2) internals
|
423
|
+
# @param base_obj [Entity|Integer] Base node|id to find.
|
424
|
+
# @param is_feature_internal [Boolean] true:Internal nodes|false:Leaf nodes.
|
425
|
+
# @param columns [Array] Columns for SELECT clause.
|
426
|
+
# @return [Array] Found nodes.
|
427
|
+
# @return [nil] No nodes.
|
428
|
+
#
|
429
|
+
def ff_features(base_obj, is_feature_internal = false, columns = nil)
|
430
|
+
base_node = ff_resolve_nodes(base_obj)
|
431
|
+
return nil if base_node.blank?
|
432
|
+
|
433
|
+
feature_key = 'ff_is_it'
|
434
|
+
|
435
|
+
ffqq = arel_table[@_ff_queue]
|
436
|
+
ffdd = arel_table[@_ff_depth]
|
437
|
+
|
438
|
+
# exists subquery, can not work @compare_depth (avert to use coalesce())
|
439
|
+
aim_query = ff_required_columns_scope(columns)
|
440
|
+
.ff_usual_order_scope(true)
|
441
|
+
.ff_subtree_scope(base_node)
|
442
|
+
.select(ff_all_optional_columns(columns))
|
443
|
+
|
444
|
+
if is_feature_internal
|
445
|
+
aim_query = aim_query.select("@compare_depth > (@compare_depth := ff_depth) AS #{feature_key}")
|
446
|
+
else
|
447
|
+
aim_query = aim_query.select("@compare_depth <= (@compare_depth := ff_depth) AS #{feature_key}")
|
448
|
+
end
|
449
|
+
|
450
|
+
aim_query.having!(feature_key)
|
451
|
+
|
452
|
+
ff_raw_query("SET @compare_depth = #{ROOT_DEPTH}")
|
453
|
+
|
454
|
+
aim_query.all.reverse
|
455
|
+
# must use .all, because this query has @xxxx.
|
456
|
+
end
|
457
|
+
|
458
|
+
#
|
459
|
+
# Find all nodes in grove.
|
460
|
+
# @param grove_id [Integer|nil] Grove ID to find.
|
461
|
+
# @param columns [Array] Columns for SELECT clause.
|
462
|
+
# @return [ActiveRecord::Relation] Query for finding nodes.
|
463
|
+
# @todo Pagination.
|
464
|
+
#
|
465
|
+
def grove_nodes(grove_id = nil, columns = nil)
|
466
|
+
ff_usual_conditions_scope(grove_id)
|
467
|
+
.ff_usual_order_scope()
|
468
|
+
end
|
469
|
+
|
470
|
+
#
|
471
|
+
# Find all root nodes in grove.
|
472
|
+
#
|
473
|
+
# @param grove_id [Integer|nil] Grove ID to find.
|
474
|
+
# @param columns [Array] Columns for SELECT clause.
|
475
|
+
# @return [ActiveRecord::Relation] Query for finding nodes.
|
476
|
+
# @todo Pagination.
|
477
|
+
#
|
478
|
+
def roots(grove_id = nil, columns = nil)
|
479
|
+
grove_id = grove_id.to_i
|
480
|
+
ffdd = arel_table[@_ff_depth]
|
481
|
+
ff_usual_conditions_scope(grove_id)
|
482
|
+
.ff_usual_order_scope()
|
483
|
+
.where(ffdd.eq(ROOT_DEPTH))
|
484
|
+
end
|
485
|
+
|
486
|
+
#
|
487
|
+
# Find grove informations that has enable (not soft deleted and not grove deleted).
|
488
|
+
#
|
489
|
+
# @return [ActiveRecord::Relation] Query for finding grove ids.
|
490
|
+
# @todo Test.
|
491
|
+
#
|
492
|
+
def groves
|
493
|
+
return nil unless has_grove?
|
494
|
+
|
495
|
+
ffid = arel_table[@_ff_id]
|
496
|
+
ffgg = arel_table[@_ff_grove]
|
497
|
+
|
498
|
+
ff_usual_conditions_scope()
|
499
|
+
.select([ffgg, ffid.count('*').as('ff_count')])
|
500
|
+
.group([ffgg])
|
501
|
+
end
|
502
|
+
|
503
|
+
#
|
504
|
+
# Create nested nodes from subtree nodes.
|
505
|
+
#
|
506
|
+
# @param haystack_nodes [ActiveRecord::Relation|Array] Iteratorable nodes data.
|
507
|
+
# @return [Hash|Array] When has grove is Hash, otherwise Array.
|
508
|
+
#
|
509
|
+
def nested_nodes(haystack_nodes)
|
510
|
+
return {} if haystack_nodes.blank?
|
511
|
+
|
512
|
+
#
|
513
|
+
# pick up nodes by iterator
|
514
|
+
# (1) array
|
515
|
+
# (2) query
|
516
|
+
# (3) ResultSet
|
517
|
+
# return by hash [id => node]
|
518
|
+
#
|
519
|
+
sorted_nodes = ff_queue_sorted_nodes(haystack_nodes)
|
520
|
+
return {} if sorted_nodes.blank?
|
521
|
+
|
522
|
+
# ネストがroot(=1)からじゃない場合の対応
|
523
|
+
# queueで並べた先頭のdepthを暫定root depthとする
|
524
|
+
the_root_depth = sorted_nodes[0].ff_depth
|
525
|
+
|
526
|
+
sorted_nodes.each do |node|
|
527
|
+
node.nest_unset_parent
|
528
|
+
node.nest_unset_children
|
529
|
+
end
|
530
|
+
|
531
|
+
# 遡って見えているnodesを格納する配列
|
532
|
+
retro_nodes = {}
|
533
|
+
if has_grove?
|
534
|
+
sorted_nodes.each do |node|
|
535
|
+
retro_nodes[node.ff_grove] = []
|
536
|
+
end
|
537
|
+
else
|
538
|
+
retro_nodes[:singular] = []
|
539
|
+
end
|
540
|
+
|
541
|
+
# 戻り値の生成用の、IDをキーとしたhashによるnest情報
|
542
|
+
res_nested_nodes = {}
|
543
|
+
|
544
|
+
grove_key = :singular
|
545
|
+
sorted_nodes.each do |node|
|
546
|
+
the_id = node.id
|
547
|
+
depth = node.ff_depth
|
548
|
+
|
549
|
+
grove_key = node.ff_grove if has_grove?
|
550
|
+
|
551
|
+
res_nested_nodes[the_id] = node; # 今回のnodesを登録
|
552
|
+
|
553
|
+
depth_index = depth - the_root_depth # ネストがroot(=1)からじゃない場合の対応
|
554
|
+
parent_depth_index = depth_index - 1
|
555
|
+
|
556
|
+
# このnodeに親があれば、親子関係を登録
|
557
|
+
if 0 <= parent_depth_index && retro_nodes[grove_key][parent_depth_index].present?
|
558
|
+
parent_id = retro_nodes[grove_key][parent_depth_index]
|
559
|
+
|
560
|
+
res_nested_nodes[parent_id].nest_set_child_id(the_id)
|
561
|
+
res_nested_nodes[the_id].nest_set_parent_id(parent_id)
|
562
|
+
end
|
563
|
+
|
564
|
+
# 今回の深度のところまで親リストを消した上で自分を登録する
|
565
|
+
retro_nodes[grove_key] = retro_nodes[grove_key].slice(0, depth_index)
|
566
|
+
retro_nodes[grove_key][depth_index] = the_id
|
567
|
+
end
|
568
|
+
|
569
|
+
return res_nested_nodes unless has_grove?
|
570
|
+
|
571
|
+
# set grove hash
|
572
|
+
grove_res = {}
|
573
|
+
|
574
|
+
retro_nodes.keys.each do |grove|
|
575
|
+
grove_res[grove] = {}
|
576
|
+
end
|
577
|
+
|
578
|
+
res_nested_nodes.each_pair do |id, node|
|
579
|
+
grove_res[node.ff_grove][id] = node
|
580
|
+
end
|
581
|
+
|
582
|
+
grove_res
|
583
|
+
end
|
584
|
+
|
585
|
+
#
|
586
|
+
# Create nested IDs
|
587
|
+
#
|
588
|
+
# @param haystack_nodes [ActiveRecord::Relation|Array] Iteratorable nodes data.
|
589
|
+
# @return [Array] Nested IDs data.
|
590
|
+
#
|
591
|
+
def nested_ids(haystack_nodes)
|
592
|
+
res = {}
|
593
|
+
if haystack_nodes.present?
|
594
|
+
if has_grove?
|
595
|
+
haystack_nodes.each_pair do |grove_id, grove_nodes|
|
596
|
+
res[grove_id] = {}
|
597
|
+
grove_nodes.each_pair do |the_id, the_node|
|
598
|
+
res[grove_id].merge!(ff_nest_children(the_id, grove_nodes))
|
599
|
+
end
|
600
|
+
end
|
601
|
+
else
|
602
|
+
haystack_nodes.each_pair do |the_id, the_node|
|
603
|
+
res.merge!(ff_nest_children(the_id, haystack_nodes))
|
604
|
+
end
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
# return value
|
609
|
+
res
|
610
|
+
end
|
611
|
+
|
612
|
+
# inner method for recursion
|
613
|
+
# @scope private
|
614
|
+
def ff_nest_children(the_id, nodes)
|
615
|
+
return {} unless nodes.has_key?(the_id)
|
616
|
+
|
617
|
+
children_infos = {}
|
618
|
+
nodes[the_id].nest_child_ids.each do |child_id|
|
619
|
+
children_infos.merge!(ff_nest_children(child_id, nodes))
|
620
|
+
end
|
621
|
+
|
622
|
+
nodes.delete(the_id)
|
623
|
+
|
624
|
+
# return value
|
625
|
+
{the_id => children_infos}
|
626
|
+
end
|
627
|
+
|
628
|
+
private :ff_nest_children
|
629
|
+
|
630
|
+
def ff_get_last_node(grove_id)
|
631
|
+
return nil if has_grove? && grove_id.to_i <= 0
|
632
|
+
|
633
|
+
ff_usual_conditions_scope(grove_id)
|
634
|
+
.ff_usual_order_scope(true)
|
635
|
+
.ff_required_columns_scope()
|
636
|
+
.first
|
637
|
+
end
|
638
|
+
|
639
|
+
def ff_get_last_queue(grove_id = nil, nil_value = nil)
|
640
|
+
last_node = ff_get_last_node(grove_id)
|
641
|
+
|
642
|
+
return nil_value if last_node.blank?
|
643
|
+
|
644
|
+
last_node.ff_queue
|
645
|
+
end
|
646
|
+
|
647
|
+
def ff_get_boundary_node(base_node)
|
648
|
+
# create subquery conditions
|
649
|
+
boundary_queue_subquery = ff_create_boundary_queue_subquery(base_node)
|
650
|
+
# create query to get boundary node.
|
651
|
+
ff_required_columns_scope()
|
652
|
+
.ff_usual_conditions_scope(base_node.ff_grove)
|
653
|
+
.ff_usual_order_scope()
|
654
|
+
.where(arel_table[:ff_queue].in(boundary_queue_subquery))
|
655
|
+
.first
|
656
|
+
end
|
657
|
+
|
658
|
+
def ff_get_previous_node(base_node)
|
659
|
+
return nil if base_node.blank?
|
660
|
+
|
661
|
+
ffqq = arel_table[@_ff_queue]
|
662
|
+
|
663
|
+
ff_usual_conditions_scope(base_node.ff_grove)
|
664
|
+
.where(ffqq.lt(base_node.ff_queue))
|
665
|
+
.ff_usual_order_scope(true)
|
666
|
+
.ff_required_columns_scope()
|
667
|
+
.first
|
668
|
+
end
|
669
|
+
|
670
|
+
def ff_create_boundary_queue_subquery(base_node)
|
671
|
+
ffqq = arel_table[@_ff_queue]
|
672
|
+
ffdd = arel_table[@_ff_depth]
|
673
|
+
|
674
|
+
# same depth can be boundary node, therefore use LTE(<=)
|
675
|
+
ff_usual_projection(base_node.ff_grove)
|
676
|
+
.project(ffqq.minimum.as('boundary_queue'))
|
677
|
+
.where(ffdd.lteq(base_node.ff_depth))
|
678
|
+
.where(ffqq.gt (base_node.ff_queue))
|
679
|
+
end
|
680
|
+
|
681
|
+
### _createTailQueueSubquery
|
682
|
+
|
683
|
+
def ff_get_boundary_queue(base_node)
|
684
|
+
boundary_node = ff_get_boundary_node(base_node)
|
685
|
+
return nil if boundary_node.blank?
|
686
|
+
boundary_node.ff_queue
|
687
|
+
end
|
688
|
+
|
689
|
+
def ff_get_previous_queue(base_node)
|
690
|
+
previous_node = ff_get_previous_node(base_node)
|
691
|
+
|
692
|
+
return 0 if previous_node.blank?
|
693
|
+
|
694
|
+
previous_node.ff_queue
|
695
|
+
end
|
696
|
+
|
697
|
+
### _createSubtreeConditions
|
698
|
+
### _createCoalesceExpression
|
699
|
+
|
700
|
+
def ff_queue_sorted_nodes(haystack_nodes)
|
701
|
+
return [] if haystack_nodes.blank?
|
702
|
+
|
703
|
+
res_nodes = []
|
704
|
+
haystack_nodes.each { |node| res_nodes << node if node.present? }
|
705
|
+
|
706
|
+
ff_sort_with_queue!(res_nodes)
|
707
|
+
|
708
|
+
res_nodes
|
709
|
+
end
|
710
|
+
|
711
|
+
def ff_sort_with_queue!(haystack_nodes)
|
712
|
+
return if haystack_nodes.blank?
|
713
|
+
|
714
|
+
haystack_nodes.sort! do |a, b|
|
715
|
+
a.ff_queue <=> b.ff_queue
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
protected :ff_limited_subtree_depth,
|
720
|
+
:ff_features,
|
721
|
+
|
722
|
+
:ff_get_last_node,
|
723
|
+
:ff_get_last_queue,
|
724
|
+
:ff_get_boundary_node,
|
725
|
+
:ff_get_previous_node,
|
726
|
+
:ff_create_boundary_queue_subquery,
|
727
|
+
:ff_get_boundary_queue,
|
728
|
+
:ff_get_previous_queue,
|
729
|
+
:ff_queue_sorted_nodes,
|
730
|
+
|
731
|
+
:ff_sort_with_queue!
|
732
|
+
end
|
733
|
+
end
|
734
|
+
end
|
735
|
+
end
|
736
|
+
end
|