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,843 @@
|
|
1
|
+
##
|
2
|
+
#
|
3
|
+
# restructure methods for Table
|
4
|
+
#
|
5
|
+
#
|
6
|
+
module StewEucen
|
7
|
+
# Name space of Stew Eucen's Acts
|
8
|
+
module Acts
|
9
|
+
# Name space of Fertile Forest
|
10
|
+
module FertileForest
|
11
|
+
# Name space of class methods for Fertile Forest
|
12
|
+
module Table
|
13
|
+
# This module is for extending into derived class by ActiveRecord.<br>
|
14
|
+
# The caption contains "Instance Methods",
|
15
|
+
# but it means "Class Methods" of each derived class.
|
16
|
+
module Reconstructers
|
17
|
+
#
|
18
|
+
# Graft subtree nodes.
|
19
|
+
#
|
20
|
+
# @param aim_obj [Entity|Integer] Top node of subtree to graft.
|
21
|
+
# @param base_obj [Entity|Integer] Base node to calc wedged queue.
|
22
|
+
# @param kinship [Boolean|Integer] Graft position from base_obj.
|
23
|
+
# Integer: As child.
|
24
|
+
# Boolean: As sibling.
|
25
|
+
# @return [Boolean] true: Success.
|
26
|
+
# @return [Boolean] false: Failure.
|
27
|
+
#
|
28
|
+
def graft(aim_obj, base_obj, kinship = -1)
|
29
|
+
transaction do
|
30
|
+
nodes = ff_resolve_nodes([aim_obj, base_obj], true).values # refresh
|
31
|
+
aim_node = nodes[0]
|
32
|
+
base_node = nodes[1]
|
33
|
+
|
34
|
+
raise ActiveRecord::Rollback if aim_node.blank? || base_node.blank?
|
35
|
+
|
36
|
+
# pick up node for wedged node to scoot over. (can be null)
|
37
|
+
wedged_node = ff_get_wedged_node(base_node, kinship)
|
38
|
+
|
39
|
+
is_sibling = ff_is_bool(kinship)
|
40
|
+
depth_offset = base_node.ff_depth - aim_node.ff_depth + (is_sibling ? 0 : 1)
|
41
|
+
|
42
|
+
return true if ff_fit_to_graft(aim_node, wedged_node, depth_offset)
|
43
|
+
|
44
|
+
# return value
|
45
|
+
ff_scoots_over(aim_node, wedged_node, depth_offset)
|
46
|
+
end # transaction end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def ff_get_wedged_node(base_node, kinship)
|
52
|
+
is_sibling = ff_is_bool(kinship)
|
53
|
+
|
54
|
+
if is_sibling
|
55
|
+
ff_get_wedged_node_as_sibling(base_node, kinship)
|
56
|
+
else
|
57
|
+
ff_get_wedged_node_as_child(base_node, kinship)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ff_get_wedged_node_as_child(base_node, nth)
|
62
|
+
# pickup wedged node by order of children
|
63
|
+
if 0 <= nth
|
64
|
+
nth_child = ff_nth_child(base_node, nth)
|
65
|
+
return nth_child if nth_child.present?
|
66
|
+
end
|
67
|
+
|
68
|
+
ff_get_boundary_node(base_node) # can be null
|
69
|
+
end
|
70
|
+
|
71
|
+
def ff_get_wedged_node_as_sibling(base_node, after_sibling_node)
|
72
|
+
if after_sibling_node
|
73
|
+
ff_get_boundary_node(base_node)
|
74
|
+
else
|
75
|
+
base_node
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def ff_fit_to_graft(graft_node, wedged_node, depth_offset)
|
80
|
+
shift_queue = graft_node.ff_queue
|
81
|
+
shift_grove = graft_node.ff_grove
|
82
|
+
|
83
|
+
if has_grove? && shift_grove.blank?
|
84
|
+
# TODO: set error
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
# exists subquery, can not work @compare_depth (avert to use coalesce())
|
89
|
+
graft_query = ff_subtree_scope(graft_node, SUBTREE_WITH_TOP_NODE, false)
|
90
|
+
|
91
|
+
# count grafting subtree span of queue
|
92
|
+
shift_boundary_queue = ff_get_boundary_queue(graft_node)
|
93
|
+
max_queue = ff_get_last_queue(shift_grove, 0) \
|
94
|
+
if shift_boundary_queue.blank? || wedged_node.blank?
|
95
|
+
|
96
|
+
if shift_boundary_queue.blank?
|
97
|
+
shift_span = max_queue - shift_queue + 1
|
98
|
+
else
|
99
|
+
shift_span = shift_boundary_queue - shift_queue
|
100
|
+
end
|
101
|
+
|
102
|
+
if wedged_node.blank?
|
103
|
+
wedged_space = QUEUE_MAX_VALUE - max_queue
|
104
|
+
else
|
105
|
+
prev_queue = ff_get_previous_queue(wedged_node)
|
106
|
+
wedged_space = wedged_node.ff_queue - prev_queue - 1
|
107
|
+
end
|
108
|
+
|
109
|
+
# If fit to graft as it is, execute to graft.
|
110
|
+
if shift_span <= wedged_space
|
111
|
+
if wedged_node.blank?
|
112
|
+
queue_offset = max_queue - shift_queue + 1
|
113
|
+
else
|
114
|
+
queue_offset = prev_queue - shift_queue + 1
|
115
|
+
end
|
116
|
+
|
117
|
+
return false if queue_offset == 0 && depth_offset == 0
|
118
|
+
|
119
|
+
graft_query = graft_query
|
120
|
+
|
121
|
+
update_columns = []
|
122
|
+
update_columns << "ff_queue = ff_queue + #{queue_offset}" \
|
123
|
+
if queue_offset != 0
|
124
|
+
|
125
|
+
update_columns << "ff_depth = ff_depth + #{depth_offset}" \
|
126
|
+
if depth_offset != 0
|
127
|
+
|
128
|
+
return 0 < graft_query.update_all(update_columns.join(', '))
|
129
|
+
end
|
130
|
+
|
131
|
+
# try to fit to shift with evenizing.
|
132
|
+
node_count = graft_query.count
|
133
|
+
|
134
|
+
if node_count <= wedged_space
|
135
|
+
return false if node_count < 1
|
136
|
+
|
137
|
+
return false if wedged_node.blank? && depth_offset == 0
|
138
|
+
|
139
|
+
# SET fields
|
140
|
+
queue_interval = [QUEUE_DEFAULT_INTERVAL, wedged_space / node_count].min.to_i
|
141
|
+
start_queue = (wedged_node.blank? ? max_queue : prev_queue) + 1 - queue_interval
|
142
|
+
|
143
|
+
update_columns[:ff_queue] = "@forest_queue := @forest_queue + #{queue_interval}"
|
144
|
+
update_columns[:ff_depth] = "ff_depth + #{depth_offset}" \
|
145
|
+
if depth_offset != 0
|
146
|
+
|
147
|
+
# WHERE conditions
|
148
|
+
update_conditions = ff_create_usual_conditions_hash(shift_grove)
|
149
|
+
update_conditions['ff_queue >='] = shift_queue if shift_queue.present?
|
150
|
+
update_conditions['ff_queue <' ] = shift_boundary_queue if shift_boundary_queue.present?
|
151
|
+
|
152
|
+
# use raw query, because can not use ORDER BY in standard updateAll().
|
153
|
+
update_rows = ff_update_all_in_order(
|
154
|
+
update_columns,
|
155
|
+
update_conditions,
|
156
|
+
ff_usual_order_array(),
|
157
|
+
"SET @forest_queue = #{start_queue}"
|
158
|
+
)
|
159
|
+
|
160
|
+
return 0 < update_rows.to_i
|
161
|
+
end
|
162
|
+
|
163
|
+
# can not fit to shift
|
164
|
+
false
|
165
|
+
end
|
166
|
+
|
167
|
+
def ff_scoots_over(shift_node, wedged_node, depth_offset)
|
168
|
+
# find boundary node of shift node (can be null)
|
169
|
+
aim_boundary_node = ff_get_boundary_node(shift_node)
|
170
|
+
|
171
|
+
return false unless ff_can_graft_by_node(shift_node, aim_boundary_node, wedged_node)
|
172
|
+
|
173
|
+
aim_grove = shift_node.ff_grove
|
174
|
+
aim_queue = shift_node.ff_queue
|
175
|
+
|
176
|
+
max_queue = ff_get_last_queue(aim_grove, 0) \
|
177
|
+
if aim_boundary_node.blank? || wedged_node.blank?
|
178
|
+
|
179
|
+
if aim_boundary_node.blank?
|
180
|
+
aim_tail_queue = max_queue
|
181
|
+
else
|
182
|
+
aim_tail_queue = aim_boundary_node.ff_queue - 1
|
183
|
+
end
|
184
|
+
|
185
|
+
if wedged_node.blank?
|
186
|
+
wedged_tail_queue = max_queue
|
187
|
+
else
|
188
|
+
wedged_tail_queue = wedged_node.ff_queue - 1
|
189
|
+
end
|
190
|
+
|
191
|
+
# moving direction progress/retrogress
|
192
|
+
# when same queue, as retrogress. Therefore use "<=".
|
193
|
+
is_retrogression = wedged_tail_queue <= aim_queue
|
194
|
+
|
195
|
+
# moving distance
|
196
|
+
move_offset = is_retrogression \
|
197
|
+
? wedged_tail_queue - aim_queue + 1
|
198
|
+
: wedged_tail_queue - aim_tail_queue
|
199
|
+
|
200
|
+
involved_offset = (aim_tail_queue - aim_queue + 1) * (is_retrogression ? 1 : -1)
|
201
|
+
|
202
|
+
queue_offset_case = is_retrogression \
|
203
|
+
? ff_create_case_expression(
|
204
|
+
nil,
|
205
|
+
[["ff_queue < #{aim_queue}", involved_offset]],
|
206
|
+
move_offset
|
207
|
+
)
|
208
|
+
: ff_create_case_expression(
|
209
|
+
nil,
|
210
|
+
[["ff_queue <= #{aim_tail_queue}", move_offset]],
|
211
|
+
involved_offset
|
212
|
+
)
|
213
|
+
|
214
|
+
ffqq = arel_table[@_ff_queue]
|
215
|
+
ffdd = arel_table[@_ff_depth]
|
216
|
+
ffgg = arel_table[@_ff_grove]
|
217
|
+
|
218
|
+
# WHERE conditions
|
219
|
+
head_queue = is_retrogression ? wedged_tail_queue + 1 : aim_queue
|
220
|
+
tail_queue = is_retrogression ? aim_tail_queue : wedged_tail_queue
|
221
|
+
scoots_over_query = ff_usual_conditions_scope(aim_grove)
|
222
|
+
.where(ffqq.gteq(head_queue))
|
223
|
+
.where(ffqq.lteq(tail_queue))
|
224
|
+
|
225
|
+
# UPDATE SET columns
|
226
|
+
# To set depth must be firstly, because it include condition of queue.
|
227
|
+
# If to set queue firstly, depth condition is changed before set.
|
228
|
+
update_columns = []
|
229
|
+
if depth_offset != 0
|
230
|
+
depth_offset_case = ff_create_case_expression(
|
231
|
+
nil,
|
232
|
+
[[
|
233
|
+
[ffqq.gteq(aim_queue).to_sql, 'AND', ffqq.lteq(aim_tail_queue).to_sql].join(' '),
|
234
|
+
depth_offset
|
235
|
+
]],
|
236
|
+
#[["#{aim_queue} <= ff_queue AND ff_queue <= #{aim_tail_queue}", depth_offset]],
|
237
|
+
0
|
238
|
+
)
|
239
|
+
update_columns << "ff_depth = ff_depth + (#{depth_offset_case})"
|
240
|
+
#update_columns << "ff_depth = ff_depth + (#{depth_offset_case})"
|
241
|
+
end
|
242
|
+
update_columns << "ff_queue = ff_queue + (#{queue_offset_case})"
|
243
|
+
|
244
|
+
# return value
|
245
|
+
0 < scoots_over_query.update_all(update_columns.join(', '))
|
246
|
+
end
|
247
|
+
|
248
|
+
def ff_can_graft_by_node(aim_node, aim_boundary_node, wedged_node)
|
249
|
+
# When no wedged node, it means that last queue.
|
250
|
+
# In the case, can shift always
|
251
|
+
# TODO: should think with boundary node is null
|
252
|
+
return true if wedged_node.blank?
|
253
|
+
|
254
|
+
# If grove is different, can not shift.
|
255
|
+
if has_grove? && aim_node.ff_grove != wedged_node.ff_grove
|
256
|
+
# TODO: set error restructure.defferentGroves'
|
257
|
+
return false
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
# If wedged queue between the shifting subtree, can not shift.
|
262
|
+
# head < wedged < boundary
|
263
|
+
#
|
264
|
+
# can be "head == wedged". It is OK for shifting.
|
265
|
+
# because it means "depth-shifting".
|
266
|
+
#
|
267
|
+
# 2015/04/30
|
268
|
+
# float type aborted to use, because float has arithmetic error.
|
269
|
+
#
|
270
|
+
wedged_queue = wedged_node.ff_queue
|
271
|
+
|
272
|
+
# can be "head == wedged". It is OK for shifting.
|
273
|
+
# because it means "depth-shifting".
|
274
|
+
return true if wedged_queue <= aim_node.ff_queue
|
275
|
+
|
276
|
+
# In this case, must use boundary queue.
|
277
|
+
if aim_boundary_node.blank?
|
278
|
+
# TODO: set error restructure.graftIntoOwn
|
279
|
+
return false
|
280
|
+
end
|
281
|
+
|
282
|
+
# It is safe.
|
283
|
+
return true if aim_boundary_node.ff_queue < wedged_queue
|
284
|
+
|
285
|
+
# TODO: set error restructure.graftIntoOwn'
|
286
|
+
return false
|
287
|
+
end
|
288
|
+
|
289
|
+
public
|
290
|
+
|
291
|
+
#
|
292
|
+
# Reorder sibling nodes.
|
293
|
+
#
|
294
|
+
# @param args [mixed] Sibling nodes to permute.
|
295
|
+
# @return [Boolean] true: Success.
|
296
|
+
# @return [Boolean] false: Failure.
|
297
|
+
#
|
298
|
+
def permute(*args)
|
299
|
+
node_args = args.flatten
|
300
|
+
|
301
|
+
transaction do
|
302
|
+
sibling_nodes = ff_resolve_nodes(node_args, true) # refresh
|
303
|
+
|
304
|
+
# if node is only one, nothing to do.
|
305
|
+
raise ActiveRecord::Rollback if sibling_nodes.length < 2
|
306
|
+
|
307
|
+
# Aer they siblings?
|
308
|
+
current_orderd_child_nodes = siblings?(sibling_nodes.values)
|
309
|
+
|
310
|
+
raise ActiveRecord::Rollback if current_orderd_child_nodes.blank?
|
311
|
+
|
312
|
+
# if they are siblings, yield to permute.
|
313
|
+
|
314
|
+
# create array new orderd nodes.
|
315
|
+
new_orderd_sibling_nodes = []
|
316
|
+
new_orderd_ids = sibling_nodes.keys
|
317
|
+
|
318
|
+
current_orderd_child_nodes.each_pair do |the_id, the_node|
|
319
|
+
if sibling_nodes.has_key?(the_id)
|
320
|
+
picked_id = new_orderd_ids.shift
|
321
|
+
new_orderd_sibling_nodes << current_orderd_child_nodes[picked_id]
|
322
|
+
else
|
323
|
+
new_orderd_sibling_nodes << node
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# get sorted nodes of all siblings by queue.
|
328
|
+
# TODO: need or not need?
|
329
|
+
current_queue_orderd_nodes = ff_queue_sorted_nodes(current_orderd_child_nodes.values)
|
330
|
+
|
331
|
+
# calc each siblingNode information.
|
332
|
+
|
333
|
+
# get tail node
|
334
|
+
tail_node = current_queue_orderd_nodes.last
|
335
|
+
aim_grove = tail_node.ff_grove
|
336
|
+
|
337
|
+
# get total boundary queue (can be null)
|
338
|
+
siblings_boundary_queue = ff_get_boundary_queue(tail_node)
|
339
|
+
total_tail_queue = siblings_boundary_queue.blank? \
|
340
|
+
? ff_get_max_queue(aim_grove, 0)
|
341
|
+
: siblings_boundary_queue - 1
|
342
|
+
|
343
|
+
# set by current order.
|
344
|
+
node_attr_infos = {}
|
345
|
+
current_queue_orderd_nodes.each do |node|
|
346
|
+
node_attr_infos[node.id] = {}
|
347
|
+
end
|
348
|
+
|
349
|
+
last_node_index = current_queue_orderd_nodes.length - 1
|
350
|
+
|
351
|
+
node_id_hash = {}
|
352
|
+
current_queue_orderd_nodes.each.with_index do |the_node, i|
|
353
|
+
is_last = i == last_node_index
|
354
|
+
the_id = the_node.id
|
355
|
+
node_attr_infos[the_id][:is_last] = is_last
|
356
|
+
|
357
|
+
the_tail_queue = (is_last \
|
358
|
+
? total_tail_queue
|
359
|
+
: (current_queue_orderd_nodes[i + 1].ff_queue - 1)
|
360
|
+
)
|
361
|
+
node_attr_infos[the_id][:tail_queue] = the_tail_queue
|
362
|
+
|
363
|
+
# calc queue-width each sibling
|
364
|
+
node_attr_infos[the_id][:queue_width] = the_tail_queue - the_node.ff_queue + 1
|
365
|
+
|
366
|
+
# must use &$xxxx, because do not clone node instance.
|
367
|
+
node_id_hash[the_id] = the_node
|
368
|
+
end
|
369
|
+
|
370
|
+
# get shifted range of queues
|
371
|
+
range_queue_head = current_queue_orderd_nodes.first.ff_queue
|
372
|
+
|
373
|
+
# calc moving queue span for each node.
|
374
|
+
has_changed = false
|
375
|
+
reduce_queue = range_queue_head # default value of new queue.
|
376
|
+
new_orderd_sibling_nodes.each do |the_node|
|
377
|
+
update_id = the_node.id
|
378
|
+
off = reduce_queue - node_id_hash[update_id].ff_queue
|
379
|
+
|
380
|
+
node_attr_infos[update_id][:ff_offset] = off
|
381
|
+
has_changed = true if off != 0
|
382
|
+
|
383
|
+
reduce_queue += node_attr_infos[update_id][:queue_width]
|
384
|
+
end
|
385
|
+
|
386
|
+
# no move, no update.
|
387
|
+
return false unless has_changed
|
388
|
+
|
389
|
+
# create case for update by original order of queue.
|
390
|
+
when_hash = ['CASE']
|
391
|
+
current_queue_orderd_nodes.each do |node|
|
392
|
+
orign_id = node.id
|
393
|
+
aim_info = node_attr_infos[orign_id]
|
394
|
+
|
395
|
+
off = aim_info[:ff_offset]
|
396
|
+
if aim_info[:is_last]
|
397
|
+
when_hash << "ELSE #{off}"
|
398
|
+
else
|
399
|
+
when_hash << "WHEN ff_queue <= #{aim_info[:tail_queue]} THEN #{off}"
|
400
|
+
end
|
401
|
+
end
|
402
|
+
when_hash <<= 'END'
|
403
|
+
|
404
|
+
case_string = when_hash.join(' ')
|
405
|
+
|
406
|
+
# execute to update all
|
407
|
+
# lteq(total_tail_queue) is for max_queue
|
408
|
+
ffqq = arel_table[@_ff_queue]
|
409
|
+
res = ff_usual_conditions_scope(aim_grove)
|
410
|
+
.ff_usual_order_scope()
|
411
|
+
.where(ffqq.gteq(range_queue_head))
|
412
|
+
.where(ffqq.lteq(total_tail_queue))
|
413
|
+
.update_all("ff_queue = ff_queue + (#{case_string})")
|
414
|
+
|
415
|
+
res
|
416
|
+
end # transaction end
|
417
|
+
end
|
418
|
+
|
419
|
+
#
|
420
|
+
# Permute in siblings as "Move To".
|
421
|
+
# @param node_obj [Entity|Integer] Moved node by Entity|id.
|
422
|
+
# @param nth [Integer] Move rank in sibling. (-1:As last sibling)
|
423
|
+
# @return [Boolean] true: Success.
|
424
|
+
# @return [Boolean] false: Failure.
|
425
|
+
#
|
426
|
+
def move_to(node_obj, nth = -1)
|
427
|
+
ff_move_node(node_obj, nth, false) # false: move to
|
428
|
+
end
|
429
|
+
|
430
|
+
#
|
431
|
+
# Permute in siblings as "Move By".
|
432
|
+
# @param node_obj [Entity|Integer] Moved node by Entity|id.
|
433
|
+
# @param step [Integer] Moving offset.
|
434
|
+
# @return [Boolean] true: Success.
|
435
|
+
# @return [Boolean] false: Failure.
|
436
|
+
#
|
437
|
+
def move_by(node_obj, step)
|
438
|
+
ff_move_node(node_obj, step, true) # true: move by
|
439
|
+
end
|
440
|
+
|
441
|
+
protected
|
442
|
+
|
443
|
+
def ff_move_node(node_obj, move_number, as_move_by = false)
|
444
|
+
aim_node = ff_resolve_nodes(node_obj)
|
445
|
+
return false if aim_node.blank?
|
446
|
+
|
447
|
+
siblings_query = siblings_of(aim_node, [@_id])
|
448
|
+
return false if siblings_query.blank?
|
449
|
+
sibling_nodes = siblings_query.all
|
450
|
+
|
451
|
+
aim_id = aim_node.id
|
452
|
+
|
453
|
+
move_number = move_number.to_i
|
454
|
+
|
455
|
+
# get orderd rank from move_number
|
456
|
+
if as_move_by
|
457
|
+
return false if move_number == 0
|
458
|
+
|
459
|
+
nth = nil
|
460
|
+
sibling_nodes.each.with_index do |node, i|
|
461
|
+
if node.id == aim_id
|
462
|
+
nth = i
|
463
|
+
break
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
return false if nth.nil?
|
468
|
+
|
469
|
+
nth = [0, nth + move_number].max
|
470
|
+
nth = -1 if sibling_nodes.length <= nth
|
471
|
+
else
|
472
|
+
nth = move_number
|
473
|
+
end
|
474
|
+
|
475
|
+
nth = sibling_nodes.length - 1 if nth < 0
|
476
|
+
|
477
|
+
return false if sibling_nodes.length <= nth
|
478
|
+
|
479
|
+
return false if sibling_nodes[nth].id == aim_id
|
480
|
+
|
481
|
+
new_orderd_nodes = []
|
482
|
+
sibling_nodes.each do |node|
|
483
|
+
new_orderd_nodes << node if node.id != aim_id
|
484
|
+
end
|
485
|
+
|
486
|
+
new_orderd_nodes.insert(nth, aim_node)
|
487
|
+
|
488
|
+
permute(new_orderd_nodes)
|
489
|
+
end
|
490
|
+
|
491
|
+
public
|
492
|
+
|
493
|
+
#
|
494
|
+
# Remove the node and shift depth of descendant nodes.
|
495
|
+
# soft delete
|
496
|
+
# (1) soft delete
|
497
|
+
# (2) grove delete
|
498
|
+
# (3) normal delete
|
499
|
+
# @param node_obj [Entity|Integer] Node to remove.
|
500
|
+
# @return [Boolean] true: Success.
|
501
|
+
# @return [Boolean] false: Failure.
|
502
|
+
#
|
503
|
+
def remove(node_obj)
|
504
|
+
transaction do
|
505
|
+
remove_node = ff_resolve_nodes(node_obj, true) # refresh
|
506
|
+
raise ActiveRecord::Rollback if remove_node.blank? # nil as dubious
|
507
|
+
|
508
|
+
aim_queue = remove_node.ff_queue
|
509
|
+
aim_depth = remove_node.ff_depth
|
510
|
+
aim_grove = remove_node.ff_grove
|
511
|
+
|
512
|
+
# get range of descendants for shifting these depth
|
513
|
+
if aim_depth == ROOT_DEPTH
|
514
|
+
offset_depth = 1
|
515
|
+
else
|
516
|
+
parent_node = genitor(remove_node)
|
517
|
+
raise ActiveRecord::Rollback if parent_node.blank?
|
518
|
+
|
519
|
+
offset_depth = aim_depth - parent_node.ff_depth
|
520
|
+
end
|
521
|
+
|
522
|
+
# for soft delete
|
523
|
+
# can not use subquery for same table in UPDATE
|
524
|
+
# Mysql2::Error: You can't specify target table 'categories' for update in FROM clause:
|
525
|
+
remove_query = ff_subtree_scope(remove_node, true, false)
|
526
|
+
|
527
|
+
ffdd = arel_table[@_ff_depth]
|
528
|
+
|
529
|
+
update_columns = []
|
530
|
+
depth_value = ff_create_case_expression(
|
531
|
+
@_ff_queue,
|
532
|
+
[[aim_queue, 0]], # when then
|
533
|
+
"ff_depth - #{offset_depth}" # else value
|
534
|
+
)
|
535
|
+
update_columns << "ff_depth = (#{depth_value})"
|
536
|
+
|
537
|
+
if has_soft_delete? || enable_grove_delete?
|
538
|
+
if has_soft_delete?
|
539
|
+
delete_value = ff_create_case_expression(
|
540
|
+
@_ff_queue,
|
541
|
+
[[aim_queue, ff_options[:delete_value]]], # when then
|
542
|
+
@_ff_soft_delete # else value
|
543
|
+
)
|
544
|
+
update_columns << "#{@_ff_soft_delete} = (#{delete_value})"
|
545
|
+
else
|
546
|
+
delete_value = ff_create_case_expression(
|
547
|
+
@_ff_queue,
|
548
|
+
[[aim_queue, -1]], # when then
|
549
|
+
1 # else value
|
550
|
+
)
|
551
|
+
update_columns << "#{@_ff_grove} = #{@_ff_grove} * (#{delete_value})"
|
552
|
+
end
|
553
|
+
|
554
|
+
res = remove_query.update_all(update_columns.join(', '))
|
555
|
+
|
556
|
+
# hard delete
|
557
|
+
else
|
558
|
+
update_res = remove_query.update_all(update_columns.join(', '))
|
559
|
+
res = delete_all(@_id => remove_node.id)
|
560
|
+
end
|
561
|
+
|
562
|
+
res
|
563
|
+
end # tansaction end
|
564
|
+
end
|
565
|
+
|
566
|
+
#
|
567
|
+
# Prune subtree nodes.
|
568
|
+
# @param base_obj [Entity|Integer] Top node to prune.
|
569
|
+
# @param with_top [Boolean] Include base node in return query.
|
570
|
+
# @return [Boolean] true: Success.
|
571
|
+
# @return [Boolean] false: Failure.
|
572
|
+
#
|
573
|
+
def prune(base_obj, with_top = false)
|
574
|
+
transaction do
|
575
|
+
aim_node = ff_resolve_nodes(base_obj, true) # refresh
|
576
|
+
raise ActiveRecord::Rollback if aim_node.blank? # nil as dubious
|
577
|
+
|
578
|
+
aim_queue = aim_node.ff_queue
|
579
|
+
aim_depth = aim_node.ff_depth
|
580
|
+
aim_grove = aim_node.ff_grove
|
581
|
+
|
582
|
+
# boundry queue (can be nil)
|
583
|
+
aim_boundary_queue = ff_get_boundary_queue(aim_node)
|
584
|
+
|
585
|
+
# for soft delete
|
586
|
+
# can not use subquery for same table in UPDATE
|
587
|
+
# Mysql2::Error: You can't specify target table 'categories' for update in FROM clause:
|
588
|
+
prune_query = ff_subtree_scope(aim_node, with_top, false)
|
589
|
+
|
590
|
+
# soft delete
|
591
|
+
if has_soft_delete?
|
592
|
+
delete_key = @_ff_soft_delete
|
593
|
+
res = prune_query.update_all("#{delete_key} = #{ff_options[:delete_value]}")
|
594
|
+
elsif enable_grove_delete?
|
595
|
+
grove_key = @_ff_grove
|
596
|
+
res = prune_query.update_all("#{grove_key} = #{grove_key} * -1")
|
597
|
+
else
|
598
|
+
res = prune_query.delete_all
|
599
|
+
end
|
600
|
+
|
601
|
+
res
|
602
|
+
end # tansaction end
|
603
|
+
end
|
604
|
+
|
605
|
+
#
|
606
|
+
# Extinguish (remove top node and the descendant nodes).
|
607
|
+
# @param base_obj [Entity|Integer] Top node to extinguish.
|
608
|
+
# @return [Boolean] true: Success.
|
609
|
+
# @return [Boolean] false: Failure.
|
610
|
+
#
|
611
|
+
def extinguish(base_obj)
|
612
|
+
prune(base_obj, SUBTREE_WITH_TOP_NODE)
|
613
|
+
end
|
614
|
+
|
615
|
+
#
|
616
|
+
# Pollard (remove the descendant nodes).
|
617
|
+
# @param base_obj [Entity|Integer] Top node to pollard.
|
618
|
+
# @return [Boolean] true: Success.
|
619
|
+
# @return [Boolean] false: Failure.
|
620
|
+
#
|
621
|
+
def pollard(base_obj)
|
622
|
+
prune(base_obj, SUBTREE_WITHOUT_TOP_NODE)
|
623
|
+
end
|
624
|
+
|
625
|
+
######################################################################
|
626
|
+
|
627
|
+
#
|
628
|
+
# Normalize ff_queue fields in ordered grove.
|
629
|
+
# @param node_obj [Entity|Integer] Start node.
|
630
|
+
# @param boundary_node_obj [Entity|Integer] Boundary node.
|
631
|
+
# @return [Boolean] true: Success.
|
632
|
+
# @return [Boolean] false: Failure.
|
633
|
+
#
|
634
|
+
def normalize_queue(node_obj = nil, boundary_node_obj = nil)
|
635
|
+
transaction do
|
636
|
+
# nodes can be nil
|
637
|
+
aim_node = ff_resolve_nodes(node_obj)
|
638
|
+
aim_boundary_node = ff_resolve_nodes(boundary_node_obj)
|
639
|
+
|
640
|
+
aim_top_queue = aim_node .blank? ? nil : aim_node .ff_queue
|
641
|
+
aim_boundary_queue = aim_boundary_node.blank? ? nil : aim_boundary_node.ff_queue
|
642
|
+
|
643
|
+
res = ff_evenize(aim_node.ff_grove, aim_top_queue, aim_boundary_queue, 0) # 0: no appmend node
|
644
|
+
|
645
|
+
# return value
|
646
|
+
if res.present?
|
647
|
+
res[EVENIZE_AFFECTED_ROWS_KEY]
|
648
|
+
else
|
649
|
+
false
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
protected
|
655
|
+
|
656
|
+
def ff_evenize(
|
657
|
+
grove_id,
|
658
|
+
aim_queue,
|
659
|
+
aim_boundary_queue,
|
660
|
+
add_count,
|
661
|
+
rear_justified = false
|
662
|
+
)
|
663
|
+
return nil if has_grove? && grove_id.to_i <= 0
|
664
|
+
|
665
|
+
# can evenize?
|
666
|
+
# 1.0 <= (boundaryQueue - headQueue) / (updatedNodeCount + appendNodeCount)
|
667
|
+
can_evenize = ff_can_evenize(grove_id, aim_queue, aim_boundary_queue, add_count)
|
668
|
+
return nil if can_evenize.blank?
|
669
|
+
|
670
|
+
queue_interval = can_evenize[:queue_interval]
|
671
|
+
node_count = can_evenize[:node_count]
|
672
|
+
|
673
|
+
# execute to slide
|
674
|
+
|
675
|
+
# calc defaut value of new queues
|
676
|
+
# add_count can be 0
|
677
|
+
head_queue = aim_queue || 0
|
678
|
+
if rear_justified
|
679
|
+
start_queue = head_queue + queue_interval * (add_count - 1)
|
680
|
+
else
|
681
|
+
start_queue = head_queue - queue_interval
|
682
|
+
end
|
683
|
+
|
684
|
+
#
|
685
|
+
# excute update query
|
686
|
+
#
|
687
|
+
|
688
|
+
# exists subquery, can not work @compare_depth (avert to use coalesce())
|
689
|
+
# Use both query "SET @ffqq = xxx" and "COALESCE(@ffqq, xxx)",
|
690
|
+
# because connection is cut between SET and UPDATE
|
691
|
+
update_conditions = ff_create_usual_conditions_hash(grove_id)
|
692
|
+
update_conditions['ff_queue >='] = aim_queue if aim_queue.present?
|
693
|
+
update_conditions['ff_queue <' ] = aim_boundary_queue if aim_boundary_queue.present?
|
694
|
+
|
695
|
+
update_rows = ff_update_all_in_order(
|
696
|
+
{ff_queue: "@forest_queue := @forest_queue + #{queue_interval}"},
|
697
|
+
update_conditions,
|
698
|
+
ff_usual_order_array(),
|
699
|
+
"SET @forest_queue = #{start_queue}"
|
700
|
+
)
|
701
|
+
|
702
|
+
return nil if update_rows.to_i <= 0
|
703
|
+
|
704
|
+
# return value
|
705
|
+
{
|
706
|
+
SPROUT_VACANT_QUEUE_KEY => head_queue + (rear_justified ? 0 : queue_interval * node_count),
|
707
|
+
EVENIZE_AFFECTED_ROWS_KEY => update_rows,
|
708
|
+
}
|
709
|
+
end
|
710
|
+
|
711
|
+
def ff_can_evenize(grove_id, aim_queue, aim_boundary_queue, add_count)
|
712
|
+
# get count of node for UPDATE
|
713
|
+
ffqq = arel_table[@_ff_queue]
|
714
|
+
aim_query = ff_usual_conditions_scope(grove_id)
|
715
|
+
|
716
|
+
aim_query.where!(ffqq.gteq(aim_queue)) if aim_queue.present?
|
717
|
+
aim_query.where!(ffqq.lt(aim_boundary_queue)) if aim_boundary_queue.present?
|
718
|
+
|
719
|
+
evenizing_count = aim_query.count
|
720
|
+
|
721
|
+
head_queue = aim_queue || 0
|
722
|
+
|
723
|
+
# can nomalize?
|
724
|
+
divid_number = evenizing_count + add_count
|
725
|
+
return nil if divid_number < 1
|
726
|
+
|
727
|
+
# get boundary queue for calc
|
728
|
+
if aim_boundary_queue.present?
|
729
|
+
aim_queue_span = aim_boundary_queue - head_queue
|
730
|
+
else
|
731
|
+
max_queue = ff_get_last_queue(grove_id, 0) # 0 for nil
|
732
|
+
request_queue_span = QUEUE_DEFAULT_INTERVAL * (add_count + 1)
|
733
|
+
|
734
|
+
if QUEUE_MAX_VALUE - max_queue < request_queue_span
|
735
|
+
aim_queue_span = QUEUE_MAX_VALUE - head_queue + 1
|
736
|
+
else
|
737
|
+
aim_queue_span = max_queue - head_queue + request_queue_span
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
queue_interval = aim_queue_span / divid_number
|
742
|
+
|
743
|
+
return nil if queue_interval < 1
|
744
|
+
|
745
|
+
# return value
|
746
|
+
{
|
747
|
+
queue_interval: queue_interval,
|
748
|
+
node_count: evenizing_count,
|
749
|
+
}
|
750
|
+
end
|
751
|
+
|
752
|
+
public
|
753
|
+
|
754
|
+
#
|
755
|
+
# Normalize ff_depth fields in ordered grove.
|
756
|
+
#
|
757
|
+
# @param grove_id [Integer|nil] Grove ID to find.
|
758
|
+
# @return [Boolean] true: Success.
|
759
|
+
# @return [Boolean] false: Failure.
|
760
|
+
#
|
761
|
+
def normalize_depth(grove_id = nil)
|
762
|
+
transaction do
|
763
|
+
return false if has_grove? && grove_id.blank?
|
764
|
+
|
765
|
+
feature_key = 'ff_is_fault'
|
766
|
+
|
767
|
+
columns = [
|
768
|
+
"@compare_depth + 1 < (@compare_depth := ff_depth) AS #{feature_key}",
|
769
|
+
]
|
770
|
+
|
771
|
+
ffqq = arel_table[@_ff_queue]
|
772
|
+
ffdd = arel_table[@_ff_depth]
|
773
|
+
|
774
|
+
# exists subquery, can not work @compare_depth (avert to use coalesce())
|
775
|
+
aim_query = ff_required_columns_scope(columns)
|
776
|
+
.ff_usual_order_scope()
|
777
|
+
.ff_usual_conditions_scope(grove_id)
|
778
|
+
.having(feature_key)
|
779
|
+
.limit(1000)
|
780
|
+
|
781
|
+
# TODO: subtreeLimitSize
|
782
|
+
|
783
|
+
ff_raw_query("SET @compare_depth = #{ROOT_DEPTH}")
|
784
|
+
|
785
|
+
depth_offset_values = []
|
786
|
+
aim_query.all.each do |node|
|
787
|
+
prev_node = ff_get_previous_node(node)
|
788
|
+
|
789
|
+
next if prev_node.blank?
|
790
|
+
|
791
|
+
offset = node.ff_depth - prev_node.ff_depth - 1;
|
792
|
+
top_queue = prev_node.ff_queue
|
793
|
+
top_depth = prev_node.ff_depth
|
794
|
+
top_grove = prev_node.ff_grove if has_grove?
|
795
|
+
|
796
|
+
new_node = prev_node.clone
|
797
|
+
|
798
|
+
depth_offets = [];
|
799
|
+
(1 .. offset).each do |o|
|
800
|
+
new_node.ff_depth = top_depth + o
|
801
|
+
depth_offets << ff_get_boundary_queue(new_node)
|
802
|
+
end
|
803
|
+
|
804
|
+
grove_condition = has_grove? ? "#{@_ff_grove} = #{top_grove} AND" : '';
|
805
|
+
|
806
|
+
depth_offets.each do |boundary_queue|
|
807
|
+
boundary_condition = boundary_queue.blank? \
|
808
|
+
? ''
|
809
|
+
: "AND ff_queue < #{boundary_queue}"
|
810
|
+
|
811
|
+
depth_offset_values << "(CASE WHEN #{grove_condition} #{top_queue} < ff_queue #{boundary_condition} THEN 1 ELSE 0 END)"
|
812
|
+
end
|
813
|
+
end
|
814
|
+
|
815
|
+
return false if depth_offset_values.blank?
|
816
|
+
|
817
|
+
update_query = ff_usual_conditions_scope(grove_id)
|
818
|
+
update_rows = update_query.update_all(
|
819
|
+
"#{@_ff_depth} = #{@_ff_depth} - " + depth_offset_values.join('-')
|
820
|
+
)
|
821
|
+
|
822
|
+
# return value
|
823
|
+
0 < update_rows.to_i
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
def ff_usual_order_array(is_descent = false)
|
828
|
+
direction = is_descent ? 'DESC' : 'ASC'
|
829
|
+
res = {}
|
830
|
+
res[:ff_soft_delete] = direction if has_soft_delete?
|
831
|
+
res[:ff_grove ] = direction if has_grove?
|
832
|
+
res[:ff_depth ] = direction if is_descent
|
833
|
+
res[:ff_queue ] = direction
|
834
|
+
|
835
|
+
res
|
836
|
+
end
|
837
|
+
|
838
|
+
protected :ff_usual_order_array
|
839
|
+
end
|
840
|
+
end
|
841
|
+
end
|
842
|
+
end
|
843
|
+
end
|