Module: StewEucen::Acts::FertileForest::Table::Reconstructers
- Defined in:
- lib/fertile_forest/modules/reconstructers.rb
Overview
This module is for extending into derived class by ActiveRecord.
The caption contains “Instance Methods”, but it means “Class Methods” of each derived class.
Instance Method Summary collapse
-
#extinguish(base_obj) ⇒ Boolean
Extinguish (remove top node and the descendant nodes).
-
#graft(aim_obj, base_obj, kinship = -1)) ⇒ Boolean
Graft subtree nodes.
-
#move_by(node_obj, step) ⇒ Boolean
Permute in siblings as “Move By”.
-
#move_to(node_obj, nth = -1)) ⇒ Boolean
Permute in siblings as “Move To”.
-
#normalize_depth(grove_id = nil) ⇒ Boolean
Normalize ff_depth fields in ordered grove.
-
#normalize_queue(node_obj = nil, boundary_node_obj = nil) ⇒ Boolean
Normalize ff_queue fields in ordered grove.
-
#permute(*args) ⇒ Boolean
Reorder sibling nodes.
-
#pollard(base_obj) ⇒ Boolean
Pollard (remove the descendant nodes).
-
#prune(base_obj, with_top = false) ⇒ Boolean
Prune subtree nodes.
-
#remove(node_obj) ⇒ Boolean
Remove the node and shift depth of descendant nodes.
Instance Method Details
#extinguish(base_obj) ⇒ Boolean
Extinguish (remove top node and the descendant nodes).
611 612 613 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 611 def extinguish(base_obj) prune(base_obj, SUBTREE_WITH_TOP_NODE) end |
#graft(aim_obj, base_obj, kinship = -1)) ⇒ Boolean
Graft subtree nodes.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 28 def graft(aim_obj, base_obj, kinship = -1) transaction do nodes = ff_resolve_nodes([aim_obj, base_obj], true).values # refresh aim_node = nodes[0] base_node = nodes[1] raise ActiveRecord::Rollback if aim_node.blank? || base_node.blank? # pick up node for wedged node to scoot over. (can be null) wedged_node = ff_get_wedged_node(base_node, kinship) is_sibling = ff_is_bool(kinship) depth_offset = base_node.ff_depth - aim_node.ff_depth + (is_sibling ? 0 : 1) return true if ff_fit_to_graft(aim_node, wedged_node, depth_offset) # return value ff_scoots_over(aim_node, wedged_node, depth_offset) end # transaction end end |
#move_by(node_obj, step) ⇒ Boolean
Permute in siblings as “Move By”.
437 438 439 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 437 def move_by(node_obj, step) ff_move_node(node_obj, step, true) # true: move by end |
#move_to(node_obj, nth = -1)) ⇒ Boolean
Permute in siblings as “Move To”.
426 427 428 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 426 def move_to(node_obj, nth = -1) ff_move_node(node_obj, nth, false) # false: move to end |
#normalize_depth(grove_id = nil) ⇒ Boolean
Normalize ff_depth fields in ordered grove.
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 761 def normalize_depth(grove_id = nil) transaction do return false if has_grove? && grove_id.blank? feature_key = 'ff_is_fault' columns = [ "@compare_depth + 1 < (@compare_depth := ff_depth) AS #{feature_key}", ] ffqq = arel_table[@_ff_queue] ffdd = arel_table[@_ff_depth] # exists subquery, can not work @compare_depth (avert to use coalesce()) aim_query = ff_required_columns_scope(columns) .ff_usual_order_scope() .ff_usual_conditions_scope(grove_id) .having(feature_key) .limit(1000) # TODO: subtreeLimitSize ff_raw_query("SET @compare_depth = #{ROOT_DEPTH}") depth_offset_values = [] aim_query.all.each do |node| prev_node = ff_get_previous_node(node) next if prev_node.blank? offset = node.ff_depth - prev_node.ff_depth - 1; top_queue = prev_node.ff_queue top_depth = prev_node.ff_depth top_grove = prev_node.ff_grove if has_grove? new_node = prev_node.clone depth_offets = []; (1 .. offset).each do |o| new_node.ff_depth = top_depth + o depth_offets << ff_get_boundary_queue(new_node) end grove_condition = has_grove? ? "#{@_ff_grove} = #{top_grove} AND" : ''; depth_offets.each do |boundary_queue| boundary_condition = boundary_queue.blank? \ ? '' : "AND ff_queue < #{boundary_queue}" depth_offset_values << "(CASE WHEN #{grove_condition} #{top_queue} < ff_queue #{boundary_condition} THEN 1 ELSE 0 END)" end end return false if depth_offset_values.blank? update_query = ff_usual_conditions_scope(grove_id) update_rows = update_query.update_all( "#{@_ff_depth} = #{@_ff_depth} - " + depth_offset_values.join('-') ) # return value 0 < update_rows.to_i end end |
#normalize_queue(node_obj = nil, boundary_node_obj = nil) ⇒ Boolean
Normalize ff_queue fields in ordered grove.
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 634 def normalize_queue(node_obj = nil, boundary_node_obj = nil) transaction do # nodes can be nil aim_node = ff_resolve_nodes(node_obj) aim_boundary_node = ff_resolve_nodes(boundary_node_obj) aim_top_queue = aim_node .blank? ? nil : aim_node .ff_queue aim_boundary_queue = aim_boundary_node.blank? ? nil : aim_boundary_node.ff_queue res = ff_evenize(aim_node.ff_grove, aim_top_queue, aim_boundary_queue, 0) # 0: no appmend node # return value if res.present? res[EVENIZE_AFFECTED_ROWS_KEY] else false end end end |
#permute(*args) ⇒ Boolean
Reorder sibling nodes.
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 298 def permute(*args) node_args = args.flatten transaction do sibling_nodes = ff_resolve_nodes(node_args, true) # refresh # if node is only one, nothing to do. raise ActiveRecord::Rollback if sibling_nodes.length < 2 # Aer they siblings? current_orderd_child_nodes = siblings?(sibling_nodes.values) raise ActiveRecord::Rollback if current_orderd_child_nodes.blank? # if they are siblings, yield to permute. # create array new orderd nodes. new_orderd_sibling_nodes = [] new_orderd_ids = sibling_nodes.keys current_orderd_child_nodes.each_pair do |the_id, the_node| if sibling_nodes.has_key?(the_id) picked_id = new_orderd_ids.shift new_orderd_sibling_nodes << current_orderd_child_nodes[picked_id] else new_orderd_sibling_nodes << node end end # get sorted nodes of all siblings by queue. # TODO: need or not need? current_queue_orderd_nodes = ff_queue_sorted_nodes(current_orderd_child_nodes.values) # calc each siblingNode information. # get tail node tail_node = current_queue_orderd_nodes.last aim_grove = tail_node.ff_grove # get total boundary queue (can be null) siblings_boundary_queue = ff_get_boundary_queue(tail_node) total_tail_queue = siblings_boundary_queue.blank? \ ? ff_get_max_queue(aim_grove, 0) : siblings_boundary_queue - 1 # set by current order. node_attr_infos = {} current_queue_orderd_nodes.each do |node| node_attr_infos[node.id] = {} end last_node_index = current_queue_orderd_nodes.length - 1 node_id_hash = {} current_queue_orderd_nodes.each.with_index do |the_node, i| is_last = i == last_node_index the_id = the_node.id node_attr_infos[the_id][:is_last] = is_last the_tail_queue = (is_last \ ? total_tail_queue : (current_queue_orderd_nodes[i + 1].ff_queue - 1) ) node_attr_infos[the_id][:tail_queue] = the_tail_queue # calc queue-width each sibling node_attr_infos[the_id][:queue_width] = the_tail_queue - the_node.ff_queue + 1 # must use &$xxxx, because do not clone node instance. node_id_hash[the_id] = the_node end # get shifted range of queues range_queue_head = current_queue_orderd_nodes.first.ff_queue # calc moving queue span for each node. has_changed = false reduce_queue = range_queue_head # default value of new queue. new_orderd_sibling_nodes.each do |the_node| update_id = the_node.id off = reduce_queue - node_id_hash[update_id].ff_queue node_attr_infos[update_id][:ff_offset] = off has_changed = true if off != 0 reduce_queue += node_attr_infos[update_id][:queue_width] end # no move, no update. return false unless has_changed # create case for update by original order of queue. when_hash = ['CASE'] current_queue_orderd_nodes.each do |node| orign_id = node.id aim_info = node_attr_infos[orign_id] off = aim_info[:ff_offset] if aim_info[:is_last] when_hash << "ELSE #{off}" else when_hash << "WHEN ff_queue <= #{aim_info[:tail_queue]} THEN #{off}" end end when_hash <<= 'END' case_string = when_hash.join(' ') # execute to update all # lteq(total_tail_queue) is for max_queue ffqq = arel_table[@_ff_queue] res = ff_usual_conditions_scope(aim_grove) .ff_usual_order_scope() .where(ffqq.gteq(range_queue_head)) .where(ffqq.lteq(total_tail_queue)) .update_all("ff_queue = ff_queue + (#{case_string})") res end # transaction end end |
#pollard(base_obj) ⇒ Boolean
Pollard (remove the descendant nodes).
621 622 623 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 621 def pollard(base_obj) prune(base_obj, SUBTREE_WITHOUT_TOP_NODE) end |
#prune(base_obj, with_top = false) ⇒ Boolean
Prune subtree nodes.
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 573 def prune(base_obj, with_top = false) transaction do aim_node = ff_resolve_nodes(base_obj, true) # refresh raise ActiveRecord::Rollback if aim_node.blank? # nil as dubious aim_queue = aim_node.ff_queue aim_depth = aim_node.ff_depth aim_grove = aim_node.ff_grove # boundry queue (can be nil) aim_boundary_queue = ff_get_boundary_queue(aim_node) # for soft delete # can not use subquery for same table in UPDATE # Mysql2::Error: You can't specify target table 'categories' for update in FROM clause: prune_query = ff_subtree_scope(aim_node, with_top, false) # soft delete if has_soft_delete? delete_key = @_ff_soft_delete res = prune_query.update_all("#{delete_key} = #{[:delete_value]}") elsif enable_grove_delete? grove_key = @_ff_grove res = prune_query.update_all("#{grove_key} = #{grove_key} * -1") else res = prune_query.delete_all end res end # tansaction end end |
#remove(node_obj) ⇒ Boolean
Remove the node and shift depth of descendant nodes.
soft delete
(1) soft delete
(2) grove delete
(3) normal delete
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 |
# File 'lib/fertile_forest/modules/reconstructers.rb', line 503 def remove(node_obj) transaction do remove_node = ff_resolve_nodes(node_obj, true) # refresh raise ActiveRecord::Rollback if remove_node.blank? # nil as dubious aim_queue = remove_node.ff_queue aim_depth = remove_node.ff_depth aim_grove = remove_node.ff_grove # get range of descendants for shifting these depth if aim_depth == ROOT_DEPTH offset_depth = 1 else parent_node = genitor(remove_node) raise ActiveRecord::Rollback if parent_node.blank? offset_depth = aim_depth - parent_node.ff_depth end # for soft delete # can not use subquery for same table in UPDATE # Mysql2::Error: You can't specify target table 'categories' for update in FROM clause: remove_query = ff_subtree_scope(remove_node, true, false) ffdd = arel_table[@_ff_depth] update_columns = [] depth_value = ff_create_case_expression( @_ff_queue, [[aim_queue, 0]], # when then "ff_depth - #{offset_depth}" # else value ) update_columns << "ff_depth = (#{depth_value})" if has_soft_delete? || enable_grove_delete? if has_soft_delete? delete_value = ff_create_case_expression( @_ff_queue, [[aim_queue, [:delete_value]]], # when then @_ff_soft_delete # else value ) update_columns << "#{@_ff_soft_delete} = (#{delete_value})" else delete_value = ff_create_case_expression( @_ff_queue, [[aim_queue, -1]], # when then 1 # else value ) update_columns << "#{@_ff_grove} = #{@_ff_grove} * (#{delete_value})" end res = remove_query.update_all(update_columns.join(', ')) # hard delete else update_res = remove_query.update_all(update_columns.join(', ')) res = delete_all(@_id => remove_node.id) end res end # tansaction end end |