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