Module: Models::Lineage
- Extended by:
- ActiveSupport::Concern
- Included in:
- Article, Catalog, Communication, Employee, ItemDemandForecastAddition, LedgerAccount, LedgerProject, LineItem, Order, Quote, Role
- Defined in:
- app/concerns/models/lineage.rb
Defined Under Namespace
Modules: ClassMethods
Instance Method Summary collapse
- #ancestors ⇒ Object
- #ancestors_ids ⇒ Object
-
#children_and_roots(klass = self.class) ⇒ Object
Returns all children of the node and all roots, but removes the current node and its root.
- #descendants ⇒ Object
- #descendants_ids ⇒ Object
- #ensure_non_recursive_lineage ⇒ Object protected
-
#family_members ⇒ Object
Returns all descendants of the root.
- #generate_full_name(scope: nil, instance_method: nil) ⇒ Object
- #generate_full_name_array(scope: nil, instance_method: nil) ⇒ Object
- #lineage(separator: ' > ', scope: nil, instance_method: nil) ⇒ Object
- #lineage_array(scope: nil, instance_method: nil) ⇒ Object
- #lineage_simple(instance_method: nil) ⇒ Object
- #root ⇒ Object
-
#root_id ⇒ Object
Returns the root node of the tree.
- #self_ancestors_and_descendants ⇒ Object
-
#self_ancestors_and_descendants_ids ⇒ Object
Return self and all ancestors and all descendants.
- #self_and_ancestors ⇒ Object
- #self_and_ancestors_ids ⇒ Object
-
#self_and_children ⇒ Object
Returns children (without subchildren) and current node itself.
- #self_and_descendants ⇒ Object
- #self_and_descendants_ids ⇒ Object
-
#self_and_siblings ⇒ Object
Returns all siblings and a reference to the current node.
- #self_and_siblings_ids ⇒ Object
- #siblings ⇒ Object
- #siblings_ids ⇒ Object
Instance Method Details
#ancestors ⇒ Object
161 162 163 164 |
# File 'app/concerns/models/lineage.rb', line 161 def ancestors ids = ancestors_ids self.class.where(id: ancestors_ids).order(self.class.generate_order_by(ids)) end |
#ancestors_ids ⇒ Object
143 144 145 |
# File 'app/concerns/models/lineage.rb', line 143 def ancestors_ids self.class.ancestors_ids(id) end |
#children_and_roots(klass = self.class) ⇒ Object
Returns all children of the node and all roots, but removes the current node and its root
199 200 201 202 203 204 |
# File 'app/concerns/models/lineage.rb', line 199 def children_and_roots(klass = self.class) available = children + klass.roots available.delete(self) available.delete(root) available end |
#descendants ⇒ Object
151 152 153 154 |
# File 'app/concerns/models/lineage.rb', line 151 def descendants ids = descendants_ids self.class.where(id: ids).order(self.class.generate_order_by(ids)) end |
#descendants_ids ⇒ Object
135 136 137 |
# File 'app/concerns/models/lineage.rb', line 135 def descendants_ids self.class.descendants_ids(id) end |
#ensure_non_recursive_lineage ⇒ Object (protected)
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'app/concerns/models/lineage.rb', line 244 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 ⇒ Object
Returns all descendants of the root
194 195 196 |
# File 'app/concerns/models/lineage.rb', line 194 def family_members self.class.descendants_lin(root_id) end |
#generate_full_name(scope: nil, instance_method: nil) ⇒ Object
234 235 236 |
# File 'app/concerns/models/lineage.rb', line 234 def generate_full_name(scope: nil, instance_method: nil) lineage(scope:, instance_method:) end |
#generate_full_name_array(scope: nil, instance_method: nil) ⇒ Object
238 239 240 |
# File 'app/concerns/models/lineage.rb', line 238 def generate_full_name_array(scope: nil, instance_method: nil) lineage_array(scope:, instance_method:) end |
#lineage(separator: ' > ', scope: nil, instance_method: nil) ⇒ Object
215 216 217 218 |
# File 'app/concerns/models/lineage.rb', line 215 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) ⇒ Object
220 221 222 223 224 225 226 227 228 |
# File 'app/concerns/models/lineage.rb', line 220 def lineage_array(scope: nil, instance_method: nil) instance_method ||= :name l = [] 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) ⇒ Object
230 231 232 |
# File 'app/concerns/models/lineage.rb', line 230 def lineage_simple(instance_method: nil) lineage(separator: '-', instance_method:) end |
#root ⇒ Object
127 128 129 |
# File 'app/concerns/models/lineage.rb', line 127 def root self.class.find(root_id) if root_id end |
#root_id ⇒ Object
Returns the root node of the tree.
123 124 125 |
# File 'app/concerns/models/lineage.rb', line 123 def root_id self.class.root_ids(id).first end |
#self_ancestors_and_descendants ⇒ Object
211 212 213 |
# File 'app/concerns/models/lineage.rb', line 211 def self_ancestors_and_descendants self.class.where(id: self_ancestors_and_descendants_ids) end |
#self_ancestors_and_descendants_ids ⇒ Object
Return self and all ancestors and all descendants
207 208 209 |
# File 'app/concerns/models/lineage.rb', line 207 def self_ancestors_and_descendants_ids ancestors_ids + [id] + descendants_ids end |
#self_and_ancestors ⇒ Object
166 167 168 169 |
# File 'app/concerns/models/lineage.rb', line 166 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 ⇒ Object
147 148 149 |
# File 'app/concerns/models/lineage.rb', line 147 def self_and_ancestors_ids [id] + ancestors_ids end |
#self_and_children ⇒ Object
Returns children (without subchildren) and current node itself.
root.self_and_children # => [root, child1]
189 190 191 |
# File 'app/concerns/models/lineage.rb', line 189 def self_and_children [self] + children end |
#self_and_descendants ⇒ Object
156 157 158 159 |
# File 'app/concerns/models/lineage.rb', line 156 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 ⇒ Object
139 140 141 |
# File 'app/concerns/models/lineage.rb', line 139 def self_and_descendants_ids ([id] + descendants_ids).map(&:presence).compact end |
#self_and_siblings ⇒ Object
Returns all siblings and a reference to the current node.
subchild1.self_and_siblings # => [subchild1, subchild2]
174 175 176 |
# File 'app/concerns/models/lineage.rb', line 174 def self_and_siblings parent ? parent.children : self.class.roots end |
#self_and_siblings_ids ⇒ Object
178 179 180 |
# File 'app/concerns/models/lineage.rb', line 178 def self_and_siblings_ids parent&.children&.pluck(:id) || self.class.roots.pluck(:id) end |
#siblings ⇒ Object
131 132 133 |
# File 'app/concerns/models/lineage.rb', line 131 def siblings self_and_siblings - [self] end |
#siblings_ids ⇒ Object
182 183 184 |
# File 'app/concerns/models/lineage.rb', line 182 def siblings_ids self_and_siblings_ids.reject { |id| id == self.id } end |