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.
@@ -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