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