Module: Edge::Forest::ClassMethods

Defined in:
lib/edge/forest.rb

Instance Method Summary collapse

Instance Method Details

#find_forestObject

Finds entire forest and preloads all associations. It can be used at the end of an ActiveRecord finder chain.

Example:

# loads all locations
Location.find_forest

# loads all nodes with matching names and all there descendants
Category.where(:name => %w{clothing books electronics}).find_forest


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/edge/forest.rb', line 54

def find_forest
  new_scope = unscoped.joins("INNER JOIN all_nodes USING(#{connection.quote_column_name primary_key})")
  new_scope = new_scope.order(forest_order) if forest_order

  sql = "\#{cte_sql}\n\#{new_scope.to_sql}\n"
  records = find_by_sql sql

  records_by_id = records.each_with_object({}) { |r, h| h[r.id] = r }

  # Set all children associations to an empty array
  records.each do |r|
    children_association = r.association(:children)
    children_association.target = []
  end

  top_level_records = []

  records.each do |r|
    parent = records_by_id[r[forest_foreign_key]]
    if parent
      r.association(:parent).target = parent
      parent.association(:children).target.push(r)
    else
      top_level_records.push(r)
    end
  end

  top_level_records
end

#find_tree(id_or_ids) ⇒ Object

Finds an a tree or trees by id.

If any requested ids are not found it raises ActiveRecord::RecordNotFound.



91
92
93
94
95
96
97
98
99
100
# File 'lib/edge/forest.rb', line 91

def find_tree(id_or_ids)
  trees = where(:id => id_or_ids).find_forest
  if id_or_ids.kind_of?(Array)
    raise ActiveRecord::RecordNotFound unless trees.size == id_or_ids.size
    trees
  else
    raise ActiveRecord::RecordNotFound if trees.empty?
    trees.first
  end
end

#with_descendantsObject

Returns a new scope that includes previously scoped records and their descendants by subsuming the previous scope into a subquery

Only where scopes can precede this in a scope chain



105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/edge/forest.rb', line 105

def with_descendants
  subquery_scope = unscoped
    .joins("INNER JOIN all_nodes USING(#{connection.quote_column_name primary_key})")
    .select(primary_key)

  subquery_sql = "\#{cte_sql}\n\#{subquery_scope.to_sql}\n"

  unscoped.where "\#{connection.quote_column_name primary_key} IN (\#{subquery_sql})\n"
end