Module: Models::Lineage
- Extended by:
- ActiveSupport::Concern
- Included in:
- Article, Catalog, Communication, Employee, ItemDemandForecastAddition, LedgerAccount, LedgerProject, LineItem, Order, Quote, RmaItem, Role, RoomConfiguration
- Defined in:
- app/concerns/models/lineage.rb
Overview
ActiveSupport::Concern mixin: lineage.
Defined Under Namespace
Modules: ClassMethods
Instance Method Summary collapse
-
#ancestors ⇒ ActiveRecord::Relation
Returns all ancestors of this node, ordered by tree depth.
-
#ancestors_ids ⇒ Array<Integer>
Returns the ids of all ancestors of this node, at any depth.
-
#children_and_roots(klass = self.class) ⇒ Array<ApplicationRecord>
Returns all children of the node and all roots, but removes the current node and its root.
-
#descendants ⇒ ActiveRecord::Relation
Returns all descendants of this node, ordered by tree depth.
-
#descendants_ids ⇒ Array<Integer>
Returns the ids of all descendants of this node, at any depth.
-
#ensure_non_recursive_lineage ⇒ void
protected
Validation: rejects parent assignments that would create a circular reference.
-
#family_members ⇒ ActiveRecord::Relation
Returns all descendants of the root.
-
#generate_full_name(scope: nil, instance_method: nil) ⇒ String
Returns the full lineage path as the record's full name.
-
#generate_full_name_array(scope: nil, instance_method: nil) ⇒ Array<String>
Returns the full lineage path as an array of name segments.
-
#lineage(separator: ' > ', scope: nil, instance_method: nil) ⇒ String
Returns the full lineage path of this node as a joined string.
-
#lineage_array(scope: nil, instance_method: nil) ⇒ Array<String>
Returns the full lineage path of this node as an array of labels.
-
#lineage_simple(instance_method: nil) ⇒ String
Returns the full lineage path joined with a simple dash separator.
-
#root ⇒ ApplicationRecord?
Returns the root record of the tree containing this node.
-
#root_id ⇒ Integer?
Returns the root node of the tree.
-
#self_ancestors_and_descendants ⇒ ActiveRecord::Relation
Returns this node, all its ancestors, and all its descendants.
-
#self_ancestors_and_descendants_ids ⇒ Array<Integer>
Return self and all ancestors and all descendants.
-
#self_and_ancestors ⇒ ActiveRecord::Relation
Returns this node and all its ancestors, ordered by tree depth.
-
#self_and_ancestors_ids ⇒ Array<Integer>
Returns this node's id plus the ids of all its ancestors.
-
#self_and_children ⇒ Array<ApplicationRecord>
Returns children (without subchildren) and current node itself.
-
#self_and_descendants ⇒ ActiveRecord::Relation
Returns this node and all its descendants, ordered by tree depth.
-
#self_and_descendants_ids ⇒ Array<Integer>
Returns this node's id plus the ids of all its descendants.
-
#self_and_siblings ⇒ Array<ApplicationRecord>, ActiveRecord::Relation
Returns all siblings and a reference to the current node.
-
#self_and_siblings_ids ⇒ Array<Integer>
Returns the ids of all siblings including this node.
-
#siblings ⇒ Array<ApplicationRecord>
Returns the siblings of this node (nodes sharing the same parent), excluding self.
-
#siblings_ids ⇒ Array<Integer>
Returns the ids of all siblings excluding this node.
Instance Method Details
#ancestors ⇒ ActiveRecord::Relation
Returns all ancestors of this node, ordered by tree depth.
233 234 235 236 |
# File 'app/concerns/models/lineage.rb', line 233 def ancestors ids = ancestors_ids self.class.where(id: ancestors_ids).order(self.class.generate_order_by(ids)) end |
#ancestors_ids ⇒ Array<Integer>
Returns the ids of all ancestors of this node, at any depth.
203 204 205 |
# File 'app/concerns/models/lineage.rb', line 203 def ancestors_ids self.class.ancestors_ids(id) end |
#children_and_roots(klass = self.class) ⇒ Array<ApplicationRecord>
Returns all children of the node and all roots, but removes the current node and its root
289 290 291 292 293 294 |
# File 'app/concerns/models/lineage.rb', line 289 def children_and_roots(klass = self.class) available = children + klass.roots available.delete(self) available.delete(root) available end |
#descendants ⇒ ActiveRecord::Relation
Returns all descendants of this node, ordered by tree depth.
217 218 219 220 |
# File 'app/concerns/models/lineage.rb', line 217 def descendants ids = descendants_ids self.class.where(id: ids).order(self.class.generate_order_by(ids)) end |
#descendants_ids ⇒ Array<Integer>
Returns the ids of all descendants of this node, at any depth.
189 190 191 |
# File 'app/concerns/models/lineage.rb', line 189 def descendants_ids self.class.descendants_ids(id) end |
#ensure_non_recursive_lineage ⇒ void (protected)
This method returns an undefined value.
Validation: rejects parent assignments that would create a circular reference.
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 |
# File 'app/concerns/models/lineage.rb', line 366 def ensure_non_recursive_lineage # Dynamically determine the foreign key from the :children association fk = self.class.reflect_on_association(:children)&.foreign_key || :parent_id # Guard: Skip if this model doesn't have the foreign key attribute # This can happen if a subclass doesn't use lineage but inherits the concern return unless respond_to?(fk) parent_value = send(fk) return if parent_value.blank? # When parent changes, we need to check if the NEW parent (or any of its ancestors) # is a descendant of self. This would create a circular reference. # # For example: If A > B > C exists, and we try to set A.parent = C, # we'd get C > A > B > C (circular!) # # We check by walking up from the NEW parent to see if we encounter self visited = Set.new([id]) current_parent_id = parent_value while current_parent_id.present? if current_parent_id == id errors.add(fk, 'would create a circular reference - cannot be a descendant of itself') break end break if visited.include?(current_parent_id) # Prevent infinite loop visited.add(current_parent_id) # Look up the parent's parent (not using association to avoid caching issues) current_parent_id = self.class.where(id: current_parent_id).pick(fk) end end |
#family_members ⇒ ActiveRecord::Relation
Returns all descendants of the root
281 282 283 |
# File 'app/concerns/models/lineage.rb', line 281 def family_members self.class.descendants_lin(root_id) end |
#generate_full_name(scope: nil, instance_method: nil) ⇒ String
Returns the full lineage path as the record's full name.
348 349 350 |
# File 'app/concerns/models/lineage.rb', line 348 def generate_full_name(scope: nil, instance_method: nil) lineage(scope:, instance_method:) end |
#generate_full_name_array(scope: nil, instance_method: nil) ⇒ Array<String>
Returns the full lineage path as an array of name segments.
357 358 359 |
# File 'app/concerns/models/lineage.rb', line 357 def generate_full_name_array(scope: nil, instance_method: nil) lineage_array(scope:, instance_method:) end |
#lineage(separator: ' > ', scope: nil, instance_method: nil) ⇒ String
Returns the full lineage path of this node as a joined string.
316 317 318 319 |
# File 'app/concerns/models/lineage.rb', line 316 def lineage(separator: ' > ', scope: nil, instance_method: nil) l = lineage_array(scope:, instance_method:) l.join(separator) end |
#lineage_array(scope: nil, instance_method: nil) ⇒ Array<String>
Returns the full lineage path of this node as an array of labels.
326 327 328 329 330 331 332 333 |
# File 'app/concerns/models/lineage.rb', line 326 def lineage_array(scope: nil, instance_method: nil) instance_method ||= :name lines = ancestors lines = lines.send(scope) if scope lines = lines.map { |l| l.send(instance_method) } lines = lines.reverse lines << send(instance_method) end |
#lineage_simple(instance_method: nil) ⇒ String
Returns the full lineage path joined with a simple dash separator.
339 340 341 |
# File 'app/concerns/models/lineage.rb', line 339 def lineage_simple(instance_method: nil) lineage(separator: '-', instance_method:) end |
#root ⇒ ApplicationRecord?
Returns the root record of the tree containing this node.
175 176 177 |
# File 'app/concerns/models/lineage.rb', line 175 def root self.class.find(root_id) if root_id end |
#root_id ⇒ Integer?
Returns the root node of the tree.
168 169 170 |
# File 'app/concerns/models/lineage.rb', line 168 def root_id self.class.root_ids(id).first end |
#self_ancestors_and_descendants ⇒ ActiveRecord::Relation
Returns this node, all its ancestors, and all its descendants.
306 307 308 |
# File 'app/concerns/models/lineage.rb', line 306 def self_ancestors_and_descendants self.class.where(id: self_ancestors_and_descendants_ids) end |
#self_ancestors_and_descendants_ids ⇒ Array<Integer>
Return self and all ancestors and all descendants
299 300 301 |
# File 'app/concerns/models/lineage.rb', line 299 def self_ancestors_and_descendants_ids ancestors_ids + [id] + descendants_ids end |
#self_and_ancestors ⇒ ActiveRecord::Relation
Returns this node and all its ancestors, ordered by tree depth.
241 242 243 244 |
# File 'app/concerns/models/lineage.rb', line 241 def self_and_ancestors ids = self_and_ancestors_ids self.class.where(id: ids).order(self.class.generate_order_by(ids)) end |
#self_and_ancestors_ids ⇒ Array<Integer>
Returns this node's id plus the ids of all its ancestors.
210 211 212 |
# File 'app/concerns/models/lineage.rb', line 210 def self_and_ancestors_ids [id] + ancestors_ids end |
#self_and_children ⇒ Array<ApplicationRecord>
Returns children (without subchildren) and current node itself.
root.self_and_children # => [root, child1]
274 275 276 |
# File 'app/concerns/models/lineage.rb', line 274 def self_and_children [self] + children end |
#self_and_descendants ⇒ ActiveRecord::Relation
Returns this node and all its descendants, ordered by tree depth.
225 226 227 228 |
# File 'app/concerns/models/lineage.rb', line 225 def self_and_descendants ids = self_and_descendants_ids self.class.where(id: ids).order(self.class.generate_order_by(ids)) end |
#self_and_descendants_ids ⇒ Array<Integer>
Returns this node's id plus the ids of all its descendants.
196 197 198 |
# File 'app/concerns/models/lineage.rb', line 196 def self_and_descendants_ids ([id] + descendants_ids).filter_map(&:presence) end |
#self_and_siblings ⇒ Array<ApplicationRecord>, ActiveRecord::Relation
Returns all siblings and a reference to the current node.
subchild1.self_and_siblings # => [subchild1, subchild2]
251 252 253 |
# File 'app/concerns/models/lineage.rb', line 251 def self_and_siblings parent ? parent.children : self.class.roots end |
#self_and_siblings_ids ⇒ Array<Integer>
Returns the ids of all siblings including this node.
258 259 260 |
# File 'app/concerns/models/lineage.rb', line 258 def self_and_siblings_ids parent&.children&.ids || self.class.roots.ids end |
#siblings ⇒ Array<ApplicationRecord>
Returns the siblings of this node (nodes sharing the same parent), excluding self.
182 183 184 |
# File 'app/concerns/models/lineage.rb', line 182 def siblings self_and_siblings - [self] end |
#siblings_ids ⇒ Array<Integer>
Returns the ids of all siblings excluding this node.
265 266 267 |
# File 'app/concerns/models/lineage.rb', line 265 def siblings_ids self_and_siblings_ids.reject { |id| id == self.id } end |