Class: Gitlab::ObjectHierarchy
- Inherits:
-
Object
- Object
- Gitlab::ObjectHierarchy
- Defined in:
- lib/gitlab/object_hierarchy.rb
Overview
Retrieving of parent or child objects based on a base ActiveRecord relation.
This class uses recursive CTEs and as a result will only work on PostgreSQL.
Direct Known Subclasses
Ci::PipelineObjectHierarchy, Ci::ProcessableObjectHierarchy, WorkItems::WorkItemHierarchy
Constant Summary collapse
- DEPTH_COLUMN =
:depth
Instance Attribute Summary collapse
-
#ancestors_base ⇒ Object
readonly
Returns the value of attribute ancestors_base.
-
#descendants_base ⇒ Object
readonly
Returns the value of attribute descendants_base.
-
#model ⇒ Object
readonly
Returns the value of attribute model.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#unscoped_model ⇒ Object
readonly
Returns the value of attribute unscoped_model.
Instance Method Summary collapse
-
#all_objects ⇒ Object
Returns a relation that includes the base objects, their ancestors, and the descendants of the base objects.
-
#ancestors(upto: nil, hierarchy_order: nil) ⇒ Object
Returns the set of ancestors of a given relation, but excluding the given relation.
-
#base_and_ancestors(upto: nil, hierarchy_order: nil) ⇒ Object
Returns a relation that includes the ancestors_base set of objects and all their ancestors (recursively).
-
#base_and_descendant_ids ⇒ Object
Returns a relation that includes ID of the descendants_base set of objects and all their descendants IDs (recursively).
-
#base_and_descendants(with_depth: false) ⇒ Object
Returns a relation that includes the descendants_base set of objects and all their descendants (recursively).
-
#descendants ⇒ Object
Returns the set of descendants of a given relation, but excluding the given relation rubocop: disable CodeReuse/ActiveRecord.
-
#initialize(ancestors_base, descendants_base = ancestors_base, options: {}) ⇒ ObjectHierarchy
constructor
ancestors_base - An instance of ActiveRecord::Relation for which to get parent objects.
-
#max_descendants_depth ⇒ Object
Returns the maximum depth starting from the base A base object with no children has a maximum depth of ‘1`.
Constructor Details
#initialize(ancestors_base, descendants_base = ancestors_base, options: {}) ⇒ ObjectHierarchy
ancestors_base - An instance of ActiveRecord::Relation for which to
get parent objects.
descendants_base - An instance of ActiveRecord::Relation for which to
get child objects. If omitted, ancestors_base is used.
16 17 18 19 20 21 22 23 24 |
# File 'lib/gitlab/object_hierarchy.rb', line 16 def initialize(ancestors_base, descendants_base = ancestors_base, options: {}) raise ArgumentError, "Model of ancestors_base does not match model of descendants_base" if ancestors_base.model != descendants_base.model @ancestors_base = ancestors_base @descendants_base = descendants_base @model = ancestors_base.model @unscoped_model = @model.unscoped @options = end |
Instance Attribute Details
#ancestors_base ⇒ Object (readonly)
Returns the value of attribute ancestors_base.
10 11 12 |
# File 'lib/gitlab/object_hierarchy.rb', line 10 def ancestors_base @ancestors_base end |
#descendants_base ⇒ Object (readonly)
Returns the value of attribute descendants_base.
10 11 12 |
# File 'lib/gitlab/object_hierarchy.rb', line 10 def descendants_base @descendants_base end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
10 11 12 |
# File 'lib/gitlab/object_hierarchy.rb', line 10 def model @model end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
10 11 12 |
# File 'lib/gitlab/object_hierarchy.rb', line 10 def @options end |
#unscoped_model ⇒ Object (readonly)
Returns the value of attribute unscoped_model.
10 11 12 |
# File 'lib/gitlab/object_hierarchy.rb', line 10 def unscoped_model @unscoped_model end |
Instance Method Details
#all_objects ⇒ Object
Returns a relation that includes the base objects, their ancestors, and the descendants of the base objects.
The resulting query will roughly look like the following:
WITH RECURSIVE ancestors AS ( ... ),
descendants AS ( ... )
SELECT *
FROM (
SELECT *
FROM ancestors namespaces
UNION
SELECT *
FROM descendants namespaces
) groups;
Using this approach allows us to further add criteria to the relation with Rails thinking it’s selecting data the usual way.
If nested objects are not supported, ancestors_base is returned. rubocop: disable CodeReuse/ActiveRecord
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/gitlab/object_hierarchy.rb', line 127 def all_objects ancestors = base_and_ancestors_cte descendants = base_and_descendants_cte ancestors_table = ancestors.alias_to(objects_table) descendants_table = descendants.alias_to(objects_table) ancestors_scope = unscoped_model.from(ancestors_table) descendants_scope = unscoped_model.from(descendants_table) relation = unscoped_model .with .recursive(ancestors.to_arel, descendants.to_arel) .from_union([ ancestors_scope, descendants_scope ]) read_only(relation) end |
#ancestors(upto: nil, hierarchy_order: nil) ⇒ Object
Returns the set of ancestors of a given relation, but excluding the given relation
Passing an ‘upto` will stop the recursion once the specified parent_id is reached. So all ancestors lower than the specified ancestor will be included. rubocop: disable CodeReuse/ActiveRecord
47 48 49 |
# File 'lib/gitlab/object_hierarchy.rb', line 47 def ancestors(upto: nil, hierarchy_order: nil) base_and_ancestors(upto: upto, hierarchy_order: hierarchy_order).where.not(id: ancestors_base.select(:id)) end |
#base_and_ancestors(upto: nil, hierarchy_order: nil) ⇒ Object
Returns a relation that includes the ancestors_base set of objects and all their ancestors (recursively).
Passing an ‘upto` will stop the recursion once the specified parent_id is reached. So all ancestors lower than the specified ancestor will be included.
Passing a ‘hierarchy_order` with either `:asc` or `:desc` will cause the recursive query order from most nested object to root or from the root ancestor to most nested object respectively. This uses a `depth` column where `1` is defined as the depth for the base and increment as we go up each parent.
Note: By default the order is breadth-first rubocop: disable CodeReuse/ActiveRecord
67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/gitlab/object_hierarchy.rb', line 67 def base_and_ancestors(upto: nil, hierarchy_order: nil) upto_id = upto.try(:id) || upto cte = base_and_ancestors_cte(upto_id, hierarchy_order) recursive_query = if hierarchy_order # othewise depth won't be available for outer query cte.apply_to(unscoped_model.all.select(objects_table[Arel.star])).order(depth: hierarchy_order) else cte.apply_to(unscoped_model.all) end read_only(recursive_query) end |
#base_and_descendant_ids ⇒ Object
Returns a relation that includes ID of the descendants_base set of objects and all their descendants IDs (recursively). rubocop: disable CodeReuse/ActiveRecord
99 100 101 |
# File 'lib/gitlab/object_hierarchy.rb', line 99 def base_and_descendant_ids read_only(base_and_descendant_ids_cte.apply_to(unscoped_model.select(objects_table[:id]))) end |
#base_and_descendants(with_depth: false) ⇒ Object
Returns a relation that includes the descendants_base set of objects and all their descendants (recursively).
When ‘with_depth` is `true`, a `depth` column is included where it starts with `1` for the base objects and incremented as we go down the descendant tree rubocop: disable CodeReuse/ActiveRecord
88 89 90 91 92 93 |
# File 'lib/gitlab/object_hierarchy.rb', line 88 def base_and_descendants(with_depth: false) outer_select_relation = unscoped_model.all outer_select_relation = outer_select_relation.select(objects_table[Arel.star]) if with_depth # Otherwise Active Record will not select `depth` as it's not a table column read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(outer_select_relation)) end |
#descendants ⇒ Object
Returns the set of descendants of a given relation, but excluding the given relation rubocop: disable CodeReuse/ActiveRecord
29 30 31 |
# File 'lib/gitlab/object_hierarchy.rb', line 29 def descendants base_and_descendants.where.not(id: descendants_base.select(:id)) end |
#max_descendants_depth ⇒ Object
Returns the maximum depth starting from the base A base object with no children has a maximum depth of ‘1`
36 37 38 |
# File 'lib/gitlab/object_hierarchy.rb', line 36 def max_descendants_depth base_and_descendants(with_depth: true).maximum(DEPTH_COLUMN) end |