Module: Models::Lineage
- Extended by:
- ActiveSupport::Concern
- Included in:
- Article, Catalog, Communication, Employee, ItemDemandForecastAddition, LedgerAccount, LedgerProject, LineItem, Order, Quote, RmaItem, Role
- Defined in:
- app/concerns/models/lineage.rb
Overview
ActiveSupport::Concern mixin: lineage.
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
163 164 165 166 |
# File 'app/concerns/models/lineage.rb', line 163 def ancestors ids = ancestors_ids self.class.where(id: ancestors_ids).order(self.class.generate_order_by(ids)) end |
#ancestors_ids ⇒ Object
145 146 147 |
# File 'app/concerns/models/lineage.rb', line 145 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
201 202 203 204 205 206 |
# File 'app/concerns/models/lineage.rb', line 201 def children_and_roots(klass = self.class) available = children + klass.roots available.delete(self) available.delete(root) available end |
#descendants ⇒ Object
153 154 155 156 |
# File 'app/concerns/models/lineage.rb', line 153 def descendants ids = descendants_ids self.class.where(id: ids).order(self.class.generate_order_by(ids)) end |
#descendants_ids ⇒ Object
137 138 139 |
# File 'app/concerns/models/lineage.rb', line 137 def descendants_ids self.class.descendants_ids(id) end |
#ensure_non_recursive_lineage ⇒ Object (protected)
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 281 |
# File 'app/concerns/models/lineage.rb', line 245 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
196 197 198 |
# File 'app/concerns/models/lineage.rb', line 196 def family_members self.class.descendants_lin(root_id) end |
#generate_full_name(scope: nil, instance_method: nil) ⇒ Object
235 236 237 |
# File 'app/concerns/models/lineage.rb', line 235 def generate_full_name(scope: nil, instance_method: nil) lineage(scope:, instance_method:) end |
#generate_full_name_array(scope: nil, instance_method: nil) ⇒ Object
239 240 241 |
# File 'app/concerns/models/lineage.rb', line 239 def generate_full_name_array(scope: nil, instance_method: nil) lineage_array(scope:, instance_method:) end |
#lineage(separator: ' > ', scope: nil, instance_method: nil) ⇒ Object
217 218 219 220 |
# File 'app/concerns/models/lineage.rb', line 217 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
222 223 224 225 226 227 228 229 |
# File 'app/concerns/models/lineage.rb', line 222 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) ⇒ Object
231 232 233 |
# File 'app/concerns/models/lineage.rb', line 231 def lineage_simple(instance_method: nil) lineage(separator: '-', instance_method:) end |
#root ⇒ Object
129 130 131 |
# File 'app/concerns/models/lineage.rb', line 129 def root self.class.find(root_id) if root_id end |
#root_id ⇒ Object
Returns the root node of the tree.
125 126 127 |
# File 'app/concerns/models/lineage.rb', line 125 def root_id self.class.root_ids(id).first end |
#self_ancestors_and_descendants ⇒ Object
213 214 215 |
# File 'app/concerns/models/lineage.rb', line 213 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
209 210 211 |
# File 'app/concerns/models/lineage.rb', line 209 def self_ancestors_and_descendants_ids ancestors_ids + [id] + descendants_ids end |
#self_and_ancestors ⇒ Object
168 169 170 171 |
# File 'app/concerns/models/lineage.rb', line 168 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
149 150 151 |
# File 'app/concerns/models/lineage.rb', line 149 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]
191 192 193 |
# File 'app/concerns/models/lineage.rb', line 191 def self_and_children [self] + children end |
#self_and_descendants ⇒ Object
158 159 160 161 |
# File 'app/concerns/models/lineage.rb', line 158 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
141 142 143 |
# File 'app/concerns/models/lineage.rb', line 141 def self_and_descendants_ids ([id] + descendants_ids).filter_map(&:presence) end |
#self_and_siblings ⇒ Object
Returns all siblings and a reference to the current node.
subchild1.self_and_siblings # => [subchild1, subchild2]
176 177 178 |
# File 'app/concerns/models/lineage.rb', line 176 def self_and_siblings parent ? parent.children : self.class.roots end |
#self_and_siblings_ids ⇒ Object
180 181 182 |
# File 'app/concerns/models/lineage.rb', line 180 def self_and_siblings_ids parent&.children&.ids || self.class.roots.ids end |
#siblings ⇒ Object
133 134 135 |
# File 'app/concerns/models/lineage.rb', line 133 def siblings self_and_siblings - [self] end |
#siblings_ids ⇒ Object
184 185 186 |
# File 'app/concerns/models/lineage.rb', line 184 def siblings_ids self_and_siblings_ids.reject { |id| id == self.id } end |