module StewEucen::Acts::FertileForest::Table::Finders

This module is for extending into derived class by ActiveRecord.
The caption contains “Instance Methods”, but it means “Class Methods” of each derived class.

Public Instance Methods

afterbears(base_obj, columns = nil)
Alias for: descendants
ancestors(base_obj, columns = nil) click to toggle source

Find all ancestor nodes from base node (without base node).

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding ancestor nodes. @return [nil] No ancestor nodes.

# File lib/fertile_forest/modules/finders.rb, line 86
def ancestors(base_obj, columns = nil)
  trunk(base_obj, ANCESTOR_ALL, columns)
end
Also aliased as: superiors, forebears
auncles(base_obj, columns = nil)
Alias for: piblings
aunts(base_obj, columns = nil)
Alias for: piblings
cenancestors(objects, columns = nil) click to toggle source

Find cenancestor nodes of given nodes from base node.

@param objects [Array] Array of base nodes|ids to find. @param columns [Array] Columns for SELECT clause. @return [Entity] Grandparent node. @return [nil] No grandparent node. @since 1.2.0

# File lib/fertile_forest/modules/finders.rb, line 157
def cenancestors(objects, columns = nil)
  base_nodes = ff_resolve_nodes(objects)
  return nil if base_nodes.blank?

  entities = base_nodes.values

  # if bases include null, can not find.
  return nil if entities.include? nil

  # check same grove.
  if has_grove?
    groves = entities.map {|n| n.ff_grove }
    return nil if groves.min != groves.max
  end

  eldist_node = entities.first;
  aim_grove = eldist_node.ff_grove  # When no grove, nil

  ffqq = arel_table[@_ff_queue]
  ffdd = arel_table[@_ff_depth]
  ffgg = arel_table[@_ff_grove]

  queues = entities.map {|n| n.ff_queue }
  head_queue = queues.min
  tail_queue = queues.max

  # create subquery to find top-depth in range of head-tail.
  top_depth_subquery = ff_usual_projection(aim_grove)
      .project(ffdd.minimum.as('top_depth'))
      .where(ffqq.gteq(head_queue))
      .where(ffqq.lteq(tail_queue))

  # create subquery to find queues of ancestors.
  aim_group = [ffdd]
  aim_group.unshift(ffgg) if has_grove?

  cenancestor_nodes_subquery = ff_usual_projection(aim_grove)
      .project(ffqq.maximum.as('ancestor_queue'))
      .where(ffqq.lt(head_queue))
      .where(ffdd.lt(top_depth_subquery))
      .group(aim_group)

  # find nodes by ancestor queues
  # must use IN(), because trunk() is for general purpose to find ancestors
  # When one row, can use "=". When plural, can not use "=".
  # Error: SQLSTATE[21000]: Cardinality violation: 1242 Subquery returns more than 1 row
  ff_required_columns_scope()
      .ff_usual_conditions_scope(aim_grove)
      .ff_usual_order_scope()
      .where(ffqq.in(cenancestor_nodes_subquery))
      .select(ff_all_optional_columns(columns))
end
children(base_obj, columns = nil) click to toggle source

Find child nodes from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding child nodes. @return [nil] No child nodes.

# File lib/fertile_forest/modules/finders.rb, line 306
def children(base_obj, columns = nil)
  subtree(base_obj, DESCENDANTS_ONLY_CHILD, false, columns)
end
cousins(base_obj, columns = nil) click to toggle source

Find cousin nodes from base node. Note: Results includes siblngs nodes.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding cousin nodes. @return [nil] No cousin nodes. @since 1.1.0

# File lib/fertile_forest/modules/finders.rb, line 513
def cousins(base_obj, columns = nil)
  kinships(base_obj, KINSHIPS_BRANCH_LEVEL_TWO, KINSHIPS_SAME_DEPTH, columns)
end
descendants(base_obj, columns = nil) click to toggle source

Find descendant nodes from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding descendant nodes. @return [nil] No descendant nodes.

# File lib/fertile_forest/modules/finders.rb, line 294
def descendants(base_obj, columns = nil)
  subtree(base_obj, DESCENDANTS_ALL, false, columns)
end
Also aliased as: inferiors, afterbears
elder_sibling(base_obj, columns = nil) click to toggle source

Find elder sibling node from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [Entity] Elder sibling node of base node. @return [nil] No elder sibling node.

# File lib/fertile_forest/modules/finders.rb, line 571
def elder_sibling(base_obj, columns = nil)
  offset_sibling(base_obj, -1, columns)
end
externals(base_obj, columns = nil)
Alias for: leaves
forebears(base_obj, columns = nil)
Alias for: ancestors
genitor(base_obj, columns = nil) click to toggle source

Find genitor (= parent) node from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [Entity] Genitor node. @return [nil] No genitor node.

# File lib/fertile_forest/modules/finders.rb, line 98
def genitor(base_obj, columns = nil)
  trunk_query = trunk(base_obj, ANCESTOR_ONLY_PARENT, columns)

  return nil if trunk_query.blank?

  trunk_query.first
end
grandchildren(base_obj, columns = nil) click to toggle source

Find grandchild nodes from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding grandchild nodes. @return [nil] No grandchild nodes.

# File lib/fertile_forest/modules/finders.rb, line 340
def grandchildren(base_obj, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return [] if base_node.blank?

  grand_number = 2

  # return value
  subtree(
      base_node,
      grand_number,
      SUBTREE_WITHOUT_TOP_NODE,
      columns
    )
    .where(@_ff_depth => base_node.ff_depth + grand_number)
end
grandparent(base_obj, columns = nil) click to toggle source

Find grandparent node from base node.

@param base_obj [Entity|int] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [Entity] Grandparent node. @return [nil] No grandparent node.

# File lib/fertile_forest/modules/finders.rb, line 134
def grandparent(base_obj, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return nil if base_node.blank?

  grand_number = 2

  grandparent_depth = base_node.ff_depth - grand_number
  return nil if grandparent_depth < ROOT_DEPTH

  trunk_query = trunk(base_node, grand_number, columns)
  return nil if trunk_query.blank?

  trunk_query.first
end
grove_nodes(grove_id = nil, columns = nil) click to toggle source

Find all nodes in grove. @param grove_id [Integer|nil] Grove ID to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Query for finding nodes. @todo Pagination.

# File lib/fertile_forest/modules/finders.rb, line 700
def grove_nodes(grove_id = nil, columns = nil)
  ff_usual_conditions_scope(grove_id)
    .ff_usual_order_scope()
end
groves() click to toggle source

Find grove informations that has enable (not soft deleted and not grove deleted).

@return [ActiveRecord::Relation] Query for finding grove ids. @todo Test.

# File lib/fertile_forest/modules/finders.rb, line 727
def groves
  return nil unless has_grove?

  ffid = arel_table[@_ff_id]
  ffgg = arel_table[@_ff_grove]

  ff_usual_conditions_scope()
      .select([ffgg, ffid.count('*').as('ff_count')])
      .group([ffgg])
end
inferiors(base_obj, columns = nil)
Alias for: descendants
internals(base_obj, columns = nil) click to toggle source

Find internal nodes from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [Array] Basic query for finding internal nodes. @return [nil] No internal nodes.

# File lib/fertile_forest/modules/finders.rb, line 649
def internals(base_obj, columns = nil)
  ff_features(base_obj, true, columns)
end
kinships( base_obj, branch_level = KINSHIPS_BRANCH_LEVEL_ONE, depth_offset = KINSHIPS_SAME_DEPTH, columns = nil ) click to toggle source

Find any kind of kinship from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param branch_level [Integer] Branch distance of finding nodes from base nodes. @param depth_offset [Integer] Offset of kinship level of finding nodes from base nodes. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding kinship nodes. @return [nil] No kinship nodes. @since 1.1.0

# File lib/fertile_forest/modules/finders.rb, line 367
def kinships(
  base_obj,
  branch_level = KINSHIPS_BRANCH_LEVEL_ONE,
  depth_offset = KINSHIPS_SAME_DEPTH,
  columns = nil
)
  base_node = ff_resolve_nodes(base_obj)
  return nil if base_node.blank?

  aim_queue = base_node.ff_queue
  aim_depth = base_node.ff_depth
  aim_grove = base_node.ff_grove  # When no grove, nil

  top_depth = aim_depth - branch_level

  # Impossible to find.
  return nil if top_depth < ROOT_DEPTH

  return nil if branch_level + depth_offset < 0

  ffqq = arel_table[@_ff_queue]
  ffdd = arel_table[@_ff_depth]
  ffgg = arel_table[@_ff_grove]

  # create subquery
  before_nodes_subquery = ff_usual_projection(aim_grove)
      .project(ffqq.maximum.to_sql + " + 1 AS head_queue")
      .where(ffqq.lt(aim_queue))
      .where(ffdd.lteq(top_depth))

  after_nodes_subquery = ff_usual_projection(aim_grove)
      .project(ffqq.minimum.to_sql + " - 1 AS tail_queue")
      .where(ffqq.gt(aim_queue))
      .where(ffdd.lteq(top_depth))

  func_maker = Arel::Nodes::NamedFunction

  before_coalesce_condition = func_maker.new(
      'COALESCE',
      [before_nodes_subquery, 0]
  )

  after_coalesce_condition = func_maker.new(
      'COALESCE',
      [after_nodes_subquery, QUEUE_MAX_VALUE]
  )

  #
  # create column of ff_branch_level
  #
  increment_branch_level_case = ff_create_case_expression(
    [ffqq.eq(aim_queue).to_sql, 0],
    1
  )

  branch_for_level_subqueries = [increment_branch_level_case]
  ((top_depth + 1) .. (aim_depth - 1)).each do |d|
    before_nodes_branch_for_lavel_subquery = ff_usual_projection(aim_grove)
        .project("#{ffqq.maximum.to_sql} AS head_queue_#{d}")
        .where(ffqq.lt(aim_queue))
        .where(ffdd.lteq(d))

    after_nodes_branch_far_level_subquery = ff_usual_projection(aim_grove)
        .project("#{ffqq.minimum.to_sql} - 1 AS tail_queue_#{d}")
        .where(ffqq.gt(aim_queue))
        .where(ffdd.lteq(d))

    before_condition = func_maker.new(
        'COALESCE',
        [before_nodes_branch_for_lavel_subquery, 0]
    )
    after_condition = func_maker.new(
        'COALESCE',
        [after_nodes_branch_far_level_subquery, QUEUE_MAX_VALUE]
    )

    branch_for_level_subqueries << ff_create_case_expression(
      [ffqq.lt(before_condition).to_sql, 1],
      [ffqq.gt(after_condition).to_sql, 1],
      0
    )
  end

  ff_branch_level = branch_for_level_subqueries.join(' + ') + " AS #{@_ff_branch_level}";

  #
  # create columns
  #
  base_columns = ff_all_optional_columns(columns);
  base_columns << ff_branch_level

  # find nodes by ancestor queues
  ff_required_columns_scope()
      .ff_usual_conditions_scope(aim_grove)
      .ff_usual_order_scope()
      .where(ffdd.eq(aim_depth + depth_offset))
      .where(ffqq.gteq(before_coalesce_condition))
      .where(ffqq.lteq(after_coalesce_condition))
      .select(base_columns)
end
leaves(base_obj, columns = nil) click to toggle source

Find leaf nodes from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [Array] Basic query for finding leaf nodes. @return [nil] No leaf nodes. @todo Pagination, Create limit as desc.

# File lib/fertile_forest/modules/finders.rb, line 637
def leaves(base_obj, columns = nil)
  ff_features(base_obj, false, columns)
end
Also aliased as: externals, terminals
nephews(base_obj, columns = nil)
Alias for: niblings
nested_ids(haystack_nodes) click to toggle source

Create nested IDs

@param haystack_nodes [ActiveRecord::Relation|Array] Iteratorable nodes data. @return [Array] Nested IDs data.

# File lib/fertile_forest/modules/finders.rb, line 826
def nested_ids(haystack_nodes)
  res = {}
  if haystack_nodes.present?
    if has_grove?
      haystack_nodes.each_pair do |grove_id, grove_nodes|
        res[grove_id] = {}
        grove_nodes.each_pair do |the_id, the_node|
          res[grove_id].merge!(ff_nest_children(the_id, grove_nodes))
        end
      end
    else
      haystack_nodes.each_pair do |the_id, the_node|
        res.merge!(ff_nest_children(the_id, haystack_nodes))
      end
    end
  end

  # return value
  res
end
nested_nodes(haystack_nodes) click to toggle source

Create nested nodes from subtree nodes.

@param haystack_nodes [ActiveRecord::Relation|Array] Iteratorable nodes data. @return [Hash|Array] When has grove is Hash, otherwise Array.

# File lib/fertile_forest/modules/finders.rb, line 744
def nested_nodes(haystack_nodes)
  return {} if haystack_nodes.blank?

  #
  # pick up nodes by iterator
  #  (1) array
  #  (2) query
  #  (3) ResultSet
  # return by hash [id => node]
  #
  sorted_nodes = ff_queue_sorted_nodes(haystack_nodes)
  return {} if sorted_nodes.blank?

  # ネストがroot(=1)からじゃない場合の対応
  # queueで並べた先頭のdepthを暫定root depthとする
  the_root_depth = sorted_nodes[0].ff_depth

  sorted_nodes.each do |node|
    node.nest_unset_parent
    node.nest_unset_children
  end

  # 遡って見えているnodesを格納する配列
  retro_nodes = {}
  if has_grove?
    sorted_nodes.each do |node|
      retro_nodes[node.ff_grove] = []
    end
  else
    retro_nodes[:singular] = []
  end

  # 戻り値の生成用の、IDをキーとしたhashによるnest情報
  res_nested_nodes = {}

  grove_key = :singular
  sorted_nodes.each do |node|
    the_id = node.id
    depth = node.ff_depth

    grove_key = node.ff_grove if has_grove?

    res_nested_nodes[the_id] = node;  # 今回のnodesを登録

    depth_index = depth - the_root_depth   # ネストがroot(=1)からじゃない場合の対応
    parent_depth_index = depth_index - 1

    # このnodeに親があれば、親子関係を登録
    if 0 <= parent_depth_index && retro_nodes[grove_key][parent_depth_index].present?
      parent_id = retro_nodes[grove_key][parent_depth_index]

      res_nested_nodes[parent_id].nest_set_child_id(the_id)
      res_nested_nodes[the_id].nest_set_parent_id(parent_id)
    end

    # 今回の深度のところまで親リストを消した上で自分を登録する
    retro_nodes[grove_key] = retro_nodes[grove_key].slice(0, depth_index)
    retro_nodes[grove_key][depth_index] = the_id
  end

  return res_nested_nodes unless has_grove?

  # set grove hash
  grove_res = {}

  retro_nodes.keys.each do |grove|
    grove_res[grove] = {}
  end

  res_nested_nodes.each_pair do |id, node|
    grove_res[node.ff_grove][id] = node
  end

  grove_res
end
niblings(base_obj, columns = nil) click to toggle source

Find nibling nodes from base node. Note: Results includes siblngs nodes. @param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding nibling nodes. @return [nil] No nibling nodes. @since 1.1.0

# File lib/fertile_forest/modules/finders.rb, line 539
def niblings(base_obj, columns = nil)
  kinships(base_obj, KINSHIPS_BRANCH_LEVEL_ONE, KINSHIPS_CHILD_DEPTH, columns)
end
Also aliased as: nephews, nieces
nieces(base_obj, columns = nil)
Alias for: niblings
nth_child(base_obj, nth = 0, columns = nil) click to toggle source

Find nth-child node from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param nth [Integer] Order in child nodes. @param columns [Array] Columns for SELECT clause. @return [Entity] Nth-child node. @return [nil] No nth-child node.

# File lib/fertile_forest/modules/finders.rb, line 319
def nth_child(base_obj, nth = 0, columns = nil)
  children_query = children(base_obj, columns)

  nth = nth.to_i
  if nth < 0
    sibling_count = children_query.all.length
    nth = sibling_count - 1
    return nil if nth < 0
  end

  children_query.offset(nth).first
end
nth_sibling(base_obj, nth = 0, columns = nil) click to toggle source

Find nth-sibling node from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param nth [Integer] Order in child nodes. @param [Array] columns Columns for SELECT clause. @return [Entity] Nth-sibling node. @return [nil] No nth-sibling node.

# File lib/fertile_forest/modules/finders.rb, line 552
def nth_sibling(base_obj, nth = 0, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return nil if base_node.blank?

  parent_node = genitor(base_node)
  return nil if parent_node.blank?

  nth = nth.to_i
  nth_child(parent_node, nth, columns)
end
offset_sibling(base_obj, offset, columns = nil) click to toggle source

Find offsetted sibling node from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param offset [Integer] Order in child nodes. @param columns [Array] Columns for SELECT clause. @return [Entity] Offsetted sibling node from base node. @return [nil] No offsetted sibling node.

# File lib/fertile_forest/modules/finders.rb, line 596
def offset_sibling(base_obj, offset, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return nil if base_node.blank?

  parent_node = genitor(base_node)
  return nil if parent_node.blank?

  sibling_nodes = children(parent_node, [@_id]).all
  return nil if sibling_nodes.blank?

  base_id = base_node.id

  nth = nil
  Array(sibling_nodes).each.with_index do |node, i|
    if node.id == base_id
      nth = i
      break
    end
  end

  return nil if nth.nil?

  offset = offset.to_i

  # OFFSET -1 make an error
  # Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax;
  # check the manual that corresponds to your MySQL server version for the right syntax to use near '-1' at line 1
  return nil if nth + offset < 0

  nth_child(parent_node, nth + offset, columns)
end
piblings(base_obj, columns = nil) click to toggle source

Find aunt|uncle nodes from base node. Note: Results includes siblngs nodes. @param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding aunt|uncle nodes. @return [nil] No aunt|uncle nodes. @since 1.1.0

# File lib/fertile_forest/modules/finders.rb, line 526
def piblings(base_obj, columns = nil)
  kinships(base_obj, KINSHIPS_BRANCH_LEVEL_TWO, KINSHIPS_PARENT_DEPTH, columns)
end
Also aliased as: auncles, aunts, uncles
root(base_obj, columns = nil) click to toggle source

Find root node from base node.

@param base_obj [Entity|int] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [Entity] Root node. When base node is root, return base node. @return [Enil] No root node.

# File lib/fertile_forest/modules/finders.rb, line 114
def root(base_obj, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return nil if base_node.blank?

  return base_node if base_node.ff_depth == ROOT_DEPTH

  trunk_query = trunk(base_node, ANCESTOR_ONLY_ROOT, columns)
  return nil if trunk_query.blank?

  trunk_query.first
end
roots(grove_id = nil, columns = nil) click to toggle source

Find all root nodes in grove.

@param grove_id [Integer|nil] Grove ID to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Query for finding nodes. @todo Pagination.

# File lib/fertile_forest/modules/finders.rb, line 713
def roots(grove_id = nil, columns = nil)
  grove_id = grove_id.to_i
  ffdd = arel_table[@_ff_depth]
  ff_usual_conditions_scope(grove_id)
      .ff_usual_order_scope()
      .where(ffdd.eq(ROOT_DEPTH))
end
siblings(base_obj, columns = nil) click to toggle source

Find sibling nodes from base node. Note: Results includes base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding sibling nodes. @return [nil] No sibling nodes. @version 1.1.0 Replace to use kinships()

# File lib/fertile_forest/modules/finders.rb, line 500
def siblings(base_obj, columns = nil)
  kinships(base_obj, KINSHIPS_BRANCH_LEVEL_ONE, KINSHIPS_SAME_DEPTH, columns)
end
subtree(base_obj, range = DESCENDANTS_ALL, with_top = true, columns = nil) click to toggle source

Find subtree nodes from base node with ordered range.

@param base_obj [Entity|Integer] Base node|id to find. @param range [Integer] Ordered range of trunk nodes. -1:To designate as root node. @param withTop [boolean] Include base node in return query. @param fields [Array] Fields for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding subtree nodes. @return [nil] No subtree nodes.

# File lib/fertile_forest/modules/finders.rb, line 222
def subtree(base_obj, range = DESCENDANTS_ALL, with_top = true, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return [] if base_node.blank?

  aim_query = ff_required_columns_scope(columns)
    .ff_subtree_scope(
      base_node,
      with_top,
      true   # use COALESCE() must be false for children().count
    )

  ffdd = arel_table[@_ff_depth]
  limited = ff_limited_subtree_depth(
    base_node.ff_depth,
    range,
    aim_query
  )

  aim_query.where!(ffdd.lteq(limited)) if 0 < limited

  aim_query
    .select(ff_all_optional_columns(columns))
    .ff_usual_order_scope()
end
superiors(base_obj, columns = nil)
Alias for: ancestors
terminals(base_obj, columns = nil)
Alias for: leaves
trunk(base_obj, range = ANCESTOR_ALL, columns = nil) click to toggle source

Find trunk (= ancestor) nodes from base node in ordered range.

@param base_obj [Entity|Integer] Base node|id to find. @param range [Integer] Ordered range of trunk nodes.

-1:To designate as root node.

@param columns [Array] Columns for SELECT clause. @return [ActiveRecord::Relation] Basic query for finding trunk nodes. @return [nil] No trunk nodes.

# File lib/fertile_forest/modules/finders.rb, line 35
def trunk(base_obj, range = ANCESTOR_ALL, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return nil if base_node.blank?

  aim_queue = base_node.ff_queue
  aim_depth = base_node.ff_depth
  aim_grove = base_node.ff_grove  # When no grove, nil

  return nil if aim_depth == ROOT_DEPTH

  ffqq = arel_table[@_ff_queue]
  ffdd = arel_table[@_ff_depth]
  ffgg = arel_table[@_ff_grove]

  # create subquery to find queues of ancestor
  aim_subquery = ff_usual_projection(aim_grove)
      .where(ffqq.lt(aim_queue))

  if range < 0
    aim_subquery = aim_subquery.where(ffdd.eq(ROOT_DEPTH))
  else
    aim_subquery = aim_subquery.where(ffdd.lt(aim_depth))
    aim_subquery = aim_subquery.where(ffdd.gteq(aim_depth - range)) \
        if 0 < range
  end

  aim_group = [ffdd]
  aim_group.unshift(ffgg) if has_grove?

  aim_subquery = aim_subquery.project(ffqq.maximum.as('ancestor_queue'))
    .group(aim_group)

  # find nodes by ancestor queues
  # must use IN(), because trunk() is for general purpose to find ancestors
  # When one row, can use "=". When plural, can not use "=".
  # Error: SQLSTATE[21000]: Cardinality violation: 1242 Subquery returns more than 1 row
  ff_required_columns_scope()
      .ff_usual_conditions_scope(aim_grove)
      .ff_usual_order_scope()
      .where(ffqq.in(aim_subquery))
      .select(ff_all_optional_columns(columns))
end
uncles(base_obj, columns = nil)
Alias for: piblings
younger_sibling(base_obj, columns = nil) click to toggle source

Find younger sibling node from base node.

@param base_obj [Entity|Integer] Base node|id to find. @param columns [Array] Columns for SELECT clause. @return [Entity] Younger sibling node of base node. @return [nil] No younger sibling node.

# File lib/fertile_forest/modules/finders.rb, line 583
def younger_sibling(base_obj, columns = nil)
  offset_sibling(base_obj, 1, columns)
end

Protected Instance Methods

ff_create_boundary_queue_subquery(base_node) click to toggle source
# File lib/fertile_forest/modules/finders.rb, line 905
def ff_create_boundary_queue_subquery(base_node)
  ffqq = arel_table[@_ff_queue]
  ffdd = arel_table[@_ff_depth]

  # same depth can be boundary node, therefore use LTE(<=)
  ff_usual_projection(base_node.ff_grove)
      .project(ffqq.minimum.as('boundary_queue'))
      .where(ffdd.lteq(base_node.ff_depth))
      .where(ffqq.gt  (base_node.ff_queue))
end
ff_create_case_expression(*cases) click to toggle source

create case Expression @param *cases [Array] Cases as:

[[when, then], [when, then], ..., else]
# File lib/fertile_forest/modules/finders.rb, line 473
def ff_create_case_expression(*cases)
  when_hash = ['CASE']
  cases.each do |the_case|
    if the_case.instance_of?(Array)
      when_hash << 'WHEN'
      when_hash << the_case.shift
      when_hash << 'THEN'
      when_hash << the_case.shift
    else
      when_hash << "ELSE #{the_case}"
    end
  end
  when_hash << 'END'

  when_hash.join(' ')
end
ff_features(base_obj, is_feature_internal = false, columns = nil) click to toggle source

Find feature nodes in subtree from base node. feature nodes

(1) leaves
(2) internals

@param base_obj [Entity|Integer] Base node|id to find. @param is_feature_internal [Boolean] true:Internal nodes|false:Leaf nodes. @param columns [Array] Columns for SELECT clause. @return [Array] Found nodes. @return [nil] No nodes.

# File lib/fertile_forest/modules/finders.rb, line 664
def ff_features(base_obj, is_feature_internal = false, columns = nil)
  base_node = ff_resolve_nodes(base_obj)
  return nil if base_node.blank?

  feature_key = 'ff_is_it'

  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(true)
      .ff_subtree_scope(base_node)
      .select(ff_all_optional_columns(columns))

  if is_feature_internal
    aim_query = aim_query.select("@compare_depth > (@compare_depth := ff_depth) AS #{feature_key}")
  else
    aim_query = aim_query.select("@compare_depth <= (@compare_depth := ff_depth) AS #{feature_key}")
  end

  aim_query.having!(feature_key)

  ff_raw_query("SET @compare_depth = #{ROOT_DEPTH}")

  aim_query.all.reverse
  # must use .all, because this query has @xxxx.
end
ff_get_boundary_node(base_node) click to toggle source
# File lib/fertile_forest/modules/finders.rb, line 882
def ff_get_boundary_node(base_node)
  # create subquery conditions
  boundary_queue_subquery = ff_create_boundary_queue_subquery(base_node)
  # create query to get boundary node.
  ff_required_columns_scope()
      .ff_usual_conditions_scope(base_node.ff_grove)
      .ff_usual_order_scope()
      .where(arel_table[:ff_queue].in(boundary_queue_subquery))
      .first
end
ff_get_boundary_queue(base_node) click to toggle source

_createTailQueueSubquery

# File lib/fertile_forest/modules/finders.rb, line 918
def ff_get_boundary_queue(base_node)
  boundary_node = ff_get_boundary_node(base_node)
  return nil if boundary_node.blank?
  boundary_node.ff_queue
end
ff_get_last_node(grove_id) click to toggle source
# File lib/fertile_forest/modules/finders.rb, line 865
def ff_get_last_node(grove_id)
  return nil if has_grove? && grove_id.to_i <= 0

  ff_usual_conditions_scope(grove_id)
      .ff_usual_order_scope(true)
      .ff_required_columns_scope()
      .first
end
ff_get_last_queue(grove_id = nil, nil_value = nil) click to toggle source
# File lib/fertile_forest/modules/finders.rb, line 874
def ff_get_last_queue(grove_id = nil, nil_value = nil)
  last_node = ff_get_last_node(grove_id)

  return nil_value if last_node.blank?

  last_node.ff_queue
end
ff_get_previous_node(base_node) click to toggle source
# File lib/fertile_forest/modules/finders.rb, line 893
def ff_get_previous_node(base_node)
  return nil if base_node.blank?

  ffqq = arel_table[@_ff_queue]

  ff_usual_conditions_scope(base_node.ff_grove)
      .where(ffqq.lt(base_node.ff_queue))
      .ff_usual_order_scope(true)
      .ff_required_columns_scope()
      .first
end
ff_get_previous_queue(base_node) click to toggle source
# File lib/fertile_forest/modules/finders.rb, line 924
def ff_get_previous_queue(base_node)
  previous_node = ff_get_previous_node(base_node)

  return 0 if previous_node.blank?

  previous_node.ff_queue
end
ff_limited_subtree_depth(base_depth, range, subtree_query) click to toggle source

Count each depth for pagination in finding subtree nodes. @param base_depth [Integer] Depth of base node. @param range [Integer] Ordered depth offset. @param subtree_query [Projection] WHERE clause for finding subtree to count. @return [Integer] Max depth in query to find subtree nodes.

# File lib/fertile_forest/modules/finders.rb, line 254
def ff_limited_subtree_depth(base_depth, range, subtree_query)
  orderd_depth = range == 0 ? 0 : (base_depth + range)

  ffdd = arel_table[@_ff_depth]

  count_query = subtree_query
    .select(ffdd.count('*').as('depth_count'))
    .group(ffdd)

  limited = self.ff_options[:subtree_limit_size]

  total_size = 0
  limited_depth = 0

  count_query.each do |depth_entity|
    aim_depth = depth_entity.ff_depth
    aim_count = depth_entity.depth_count

    braek if limited < total_size += aim_count

    limited_depth = aim_depth
  end

  limited_depth = [limited_depth, base_depth + 1].max

  if orderd_depth == 0
    limited_depth
  else
    [limited_depth, orderd_depth].min
  end
end
ff_queue_sorted_nodes(haystack_nodes) click to toggle source

_createSubtreeConditions _createCoalesceExpression

# File lib/fertile_forest/modules/finders.rb, line 935
def ff_queue_sorted_nodes(haystack_nodes)
  return [] if haystack_nodes.blank?

  res_nodes = []
  haystack_nodes.each { |node| res_nodes << node if node.present? }

  ff_sort_with_queue!(res_nodes)

  res_nodes
end
ff_sort_with_queue!(haystack_nodes) click to toggle source
# File lib/fertile_forest/modules/finders.rb, line 946
def ff_sort_with_queue!(haystack_nodes)
  return if haystack_nodes.blank?

  haystack_nodes.sort! do |a, b|
    a.ff_queue <=> b.ff_queue
  end
end

Private Instance Methods

ff_nest_children(the_id, nodes) click to toggle source

inner method for recursion @scope private

# File lib/fertile_forest/modules/finders.rb, line 849
def ff_nest_children(the_id, nodes)
  return {} unless nodes.has_key?(the_id)

  children_infos = {}
  nodes[the_id].nest_child_ids.each do |child_id|
    children_infos.merge!(ff_nest_children(child_id, nodes))
  end

  nodes.delete(the_id)

  # return value
  {the_id => children_infos}
end