Module: Ancestry::InstanceMethods
- Defined in:
- lib/ancestry/instance_methods.rb
Instance Method Summary collapse
- #ancestor_of?(node) ⇒ Boolean
- #ancestors(depth_options = {}) ⇒ Object
- #ancestry_callbacks_disabled? ⇒ Boolean
- #ancestry_changed? ⇒ Boolean
-
#ancestry_exclude_self ⇒ Object
Validate that the ancestors don’t include itself.
-
#apply_orphan_strategy_adopt ⇒ Object
make child elements of this node, child of its parent.
-
#apply_orphan_strategy_destroy ⇒ Object
destroy all descendants if orphan strategy is destroy.
-
#apply_orphan_strategy_restrict ⇒ Object
throw an exception if it has children.
-
#apply_orphan_strategy_rootify ⇒ Object
make all children root if orphan strategy is rootify.
- #cache_depth ⇒ Object
- #child_ids ⇒ Object
- #child_of?(node) ⇒ Boolean
-
#children ⇒ Object
Children.
- #decrease_parent_counter_cache ⇒ Object
- #depth ⇒ Object
- #descendant_ids(depth_options = {}) ⇒ Object
- #descendant_of?(node) ⇒ Boolean
-
#descendants(depth_options = {}) ⇒ Object
Descendants.
- #has_children? ⇒ Boolean (also: #children?)
-
#has_parent? ⇒ Boolean
(also: #ancestors?)
Ancestors.
- #has_siblings? ⇒ Boolean (also: #siblings?)
- #in_subtree_of?(node) ⇒ Boolean
-
#increase_parent_counter_cache ⇒ Object
Counter Cache.
- #indirect_ids(depth_options = {}) ⇒ Object
- #indirect_of?(node) ⇒ Boolean
-
#indirects(depth_options = {}) ⇒ Object
Indirects.
- #is_childless? ⇒ Boolean (also: #childless?)
- #is_only_child? ⇒ Boolean (also: #only_child?)
- #is_root? ⇒ Boolean (also: #root?)
- #parent ⇒ Object
-
#parent=(parent) ⇒ Object
currently parent= does not work in after save callbacks assuming that parent hasn’t changed.
- #parent_id ⇒ Object
- #parent_id=(new_parent_id) ⇒ Object
- #parent_of?(node) ⇒ Boolean
- #path(depth_options = {}) ⇒ Object
- #path_ids ⇒ Object
- #path_ids_before_last_save ⇒ Object
- #path_ids_in_database ⇒ Object
- #root ⇒ Object
-
#root_id ⇒ Object
Root.
- #root_of?(node) ⇒ Boolean
- #sane_ancestor_ids? ⇒ Boolean
- #sibling_ids ⇒ Object
- #sibling_of?(node) ⇒ Boolean
-
#siblings ⇒ Object
Siblings.
-
#subtree(depth_options = {}) ⇒ Object
Subtree.
- #subtree_ids(depth_options = {}) ⇒ Object
-
#touch_ancestors_callback ⇒ Object
Touch each of this record’s ancestors (after save).
-
#update_descendants_with_new_ancestry ⇒ Object
Update descendants with new ancestry (after update).
- #update_parent_counter_cache ⇒ Object
-
#without_ancestry_callbacks ⇒ Object
Callback disabling.
Instance Method Details
#ancestor_of?(node) ⇒ Boolean
166 167 168 |
# File 'lib/ancestry/instance_methods.rb', line 166 def ancestor_of?(node) node.ancestor_ids.include?(id) end |
#ancestors(depth_options = {}) ⇒ Object
136 137 138 139 140 |
# File 'lib/ancestry/instance_methods.rb', line 136 def ancestors( = {}) return self.class.ancestry_base_class.none unless has_parent? self.class.ancestry_base_class.scope_depth(, depth).ordered_by_ancestry.ancestors_of(self) end |
#ancestry_callbacks_disabled? ⇒ Boolean
321 322 323 |
# File 'lib/ancestry/instance_methods.rb', line 321 def ancestry_callbacks_disabled? defined?(@disable_ancestry_callbacks) && @disable_ancestry_callbacks end |
#ancestry_changed? ⇒ Boolean
112 113 114 115 116 117 |
# File 'lib/ancestry/instance_methods.rb', line 112 def ancestry_changed? column = self.class.ancestry_column.to_s # These methods return nil if there are no changes. # This was fixed in a refactoring in rails 6.0: https://github.com/rails/rails/pull/35933 !!(will_save_change_to_attribute?(column) || saved_change_to_attribute?(column)) end |
#ancestry_exclude_self ⇒ Object
Validate that the ancestors don’t include itself
6 7 8 |
# File 'lib/ancestry/instance_methods.rb', line 6 def ancestry_exclude_self errors.add(:base, I18n.t("ancestry.exclude_self", class_name: self.class.model_name.human)) if ancestor_ids.include?(id) end |
#apply_orphan_strategy_adopt ⇒ Object
make child elements of this node, child of its parent
49 50 51 52 53 54 55 56 57 |
# File 'lib/ancestry/instance_methods.rb', line 49 def apply_orphan_strategy_adopt return if ancestry_callbacks_disabled? || new_record? descendants.each do |descendant| descendant.without_ancestry_callbacks do descendant.update_attribute :ancestor_ids, (descendant.ancestor_ids.delete_if { |x| x == id }) end end end |
#apply_orphan_strategy_destroy ⇒ Object
destroy all descendants if orphan strategy is destroy
38 39 40 41 42 43 44 45 46 |
# File 'lib/ancestry/instance_methods.rb', line 38 def apply_orphan_strategy_destroy return if ancestry_callbacks_disabled? || new_record? unscoped_descendants.ordered_by_ancestry.reverse_order.each do |descendant| descendant.without_ancestry_callbacks do descendant.destroy end end end |
#apply_orphan_strategy_restrict ⇒ Object
throw an exception if it has children
60 61 62 63 64 |
# File 'lib/ancestry/instance_methods.rb', line 60 def apply_orphan_strategy_restrict return if ancestry_callbacks_disabled? || new_record? raise(Ancestry::AncestryException, I18n.t("ancestry.cannot_delete_descendants")) unless is_childless? end |
#apply_orphan_strategy_rootify ⇒ Object
make all children root if orphan strategy is rootify
27 28 29 30 31 32 33 34 35 |
# File 'lib/ancestry/instance_methods.rb', line 27 def apply_orphan_strategy_rootify return if ancestry_callbacks_disabled? || new_record? unscoped_descendants.each do |descendant| descendant.without_ancestry_callbacks do descendant.update_attribute :ancestor_ids, descendant.ancestor_ids - path_ids end end end |
#cache_depth ⇒ Object
162 163 164 |
# File 'lib/ancestry/instance_methods.rb', line 162 def cache_depth write_attribute self.class.ancestry_base_class.depth_cache_column, depth end |
#child_ids ⇒ Object
228 229 230 |
# File 'lib/ancestry/instance_methods.rb', line 228 def child_ids children.pluck(self.class.primary_key) end |
#child_of?(node) ⇒ Boolean
242 243 244 |
# File 'lib/ancestry/instance_methods.rb', line 242 def child_of?(node) parent_id == node.id end |
#children ⇒ Object
Children
224 225 226 |
# File 'lib/ancestry/instance_methods.rb', line 224 def children self.class.ancestry_base_class.children_of(self) end |
#decrease_parent_counter_cache ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/ancestry/instance_methods.rb', line 83 def decrease_parent_counter_cache # @_trigger_destroy_callback comes from activerecord, which makes sure only once decrement when concurrent deletion. # but @_trigger_destroy_callback began after [email protected]. # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L340 # https://github.com/rails/rails/pull/14735 # https://github.com/rails/rails/pull/27248 return if defined?(@_trigger_destroy_callback) && !@_trigger_destroy_callback return if ancestry_callbacks_disabled? self.class.ancestry_base_class.decrement_counter counter_cache_column, parent_id end |
#depth ⇒ Object
158 159 160 |
# File 'lib/ancestry/instance_methods.rb', line 158 def depth ancestor_ids.size end |
#descendant_ids(depth_options = {}) ⇒ Object
276 277 278 |
# File 'lib/ancestry/instance_methods.rb', line 276 def descendant_ids( = {}) descendants().pluck(self.class.primary_key) end |
#descendant_of?(node) ⇒ Boolean
280 281 282 |
# File 'lib/ancestry/instance_methods.rb', line 280 def descendant_of?(node) ancestor_ids.include?(node.id) end |
#descendants(depth_options = {}) ⇒ Object
Descendants
272 273 274 |
# File 'lib/ancestry/instance_methods.rb', line 272 def descendants( = {}) self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(, depth).descendants_of(self) end |
#has_children? ⇒ Boolean Also known as: children?
232 233 234 |
# File 'lib/ancestry/instance_methods.rb', line 232 def has_children? children.exists? end |
#has_parent? ⇒ Boolean Also known as: ancestors?
Ancestors
107 108 109 |
# File 'lib/ancestry/instance_methods.rb', line 107 def has_parent? ancestor_ids.present? end |
#has_siblings? ⇒ Boolean Also known as: siblings?
256 257 258 |
# File 'lib/ancestry/instance_methods.rb', line 256 def has_siblings? siblings.exists? end |
#in_subtree_of?(node) ⇒ Boolean
308 309 310 |
# File 'lib/ancestry/instance_methods.rb', line 308 def in_subtree_of?(node) id == node.id || descendant_of?(node) end |
#increase_parent_counter_cache ⇒ Object
Counter Cache
79 80 81 |
# File 'lib/ancestry/instance_methods.rb', line 79 def increase_parent_counter_cache self.class.ancestry_base_class.increment_counter counter_cache_column, parent_id end |
#indirect_ids(depth_options = {}) ⇒ Object
290 291 292 |
# File 'lib/ancestry/instance_methods.rb', line 290 def indirect_ids( = {}) indirects().pluck(self.class.primary_key) end |
#indirect_of?(node) ⇒ Boolean
294 295 296 |
# File 'lib/ancestry/instance_methods.rb', line 294 def indirect_of?(node) ancestor_ids[0..-2].include?(node.id) end |
#indirects(depth_options = {}) ⇒ Object
Indirects
286 287 288 |
# File 'lib/ancestry/instance_methods.rb', line 286 def indirects( = {}) self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(, depth).indirects_of(self) end |
#is_childless? ⇒ Boolean Also known as: childless?
237 238 239 |
# File 'lib/ancestry/instance_methods.rb', line 237 def is_childless? !has_children? end |
#is_only_child? ⇒ Boolean Also known as: only_child?
261 262 263 |
# File 'lib/ancestry/instance_methods.rb', line 261 def is_only_child? !has_siblings? end |
#is_root? ⇒ Boolean Also known as: root?
213 214 215 |
# File 'lib/ancestry/instance_methods.rb', line 213 def is_root? !has_parent? end |
#parent ⇒ Object
187 188 189 190 191 192 193 |
# File 'lib/ancestry/instance_methods.rb', line 187 def parent if has_parent? unscoped_where do |scope| scope.find_by scope.primary_key => parent_id end end end |
#parent=(parent) ⇒ Object
currently parent= does not work in after save callbacks assuming that parent hasn’t changed
174 175 176 |
# File 'lib/ancestry/instance_methods.rb', line 174 def parent=(parent) self.ancestor_ids = parent ? parent.path_ids : [] end |
#parent_id ⇒ Object
182 183 184 |
# File 'lib/ancestry/instance_methods.rb', line 182 def parent_id ancestor_ids.last if has_parent? end |
#parent_id=(new_parent_id) ⇒ Object
178 179 180 |
# File 'lib/ancestry/instance_methods.rb', line 178 def parent_id=(new_parent_id) self.parent = new_parent_id.present? ? unscoped_find(new_parent_id) : nil end |
#parent_of?(node) ⇒ Boolean
195 196 197 |
# File 'lib/ancestry/instance_methods.rb', line 195 def parent_of?(node) id == node.parent_id end |
#path(depth_options = {}) ⇒ Object
154 155 156 |
# File 'lib/ancestry/instance_methods.rb', line 154 def path( = {}) self.class.ancestry_base_class.scope_depth(, depth).ordered_by_ancestry.inpath_of(self) end |
#path_ids ⇒ Object
142 143 144 |
# File 'lib/ancestry/instance_methods.rb', line 142 def path_ids ancestor_ids + [id] end |
#path_ids_before_last_save ⇒ Object
146 147 148 |
# File 'lib/ancestry/instance_methods.rb', line 146 def path_ids_before_last_save ancestor_ids_before_last_save + [id] end |
#path_ids_in_database ⇒ Object
150 151 152 |
# File 'lib/ancestry/instance_methods.rb', line 150 def path_ids_in_database ancestor_ids_in_database + [id] end |
#root ⇒ Object
205 206 207 208 209 210 211 |
# File 'lib/ancestry/instance_methods.rb', line 205 def root if has_parent? unscoped_where { |scope| scope.find_by(scope.primary_key => root_id) } || self else self end end |
#root_id ⇒ Object
Root
201 202 203 |
# File 'lib/ancestry/instance_methods.rb', line 201 def root_id has_parent? ? ancestor_ids.first : id end |
#root_of?(node) ⇒ Boolean
218 219 220 |
# File 'lib/ancestry/instance_methods.rb', line 218 def root_of?(node) id == node.root_id end |
#sane_ancestor_ids? ⇒ Boolean
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/ancestry/instance_methods.rb', line 119 def sane_ancestor_ids? current_context, self.validation_context = validation_context, nil errors.clear attribute = self.class.ancestry_column ancestry_value = send(attribute) return true unless ancestry_value self.class.validators_on(attribute).each do |validator| validator.validate_each(self, attribute, ancestry_value) end ancestry_exclude_self errors.none? ensure self.validation_context = current_context end |
#sibling_ids ⇒ Object
252 253 254 |
# File 'lib/ancestry/instance_methods.rb', line 252 def sibling_ids siblings.pluck(self.class.primary_key) end |
#sibling_of?(node) ⇒ Boolean
266 267 268 |
# File 'lib/ancestry/instance_methods.rb', line 266 def sibling_of?(node) ancestor_ids == node.ancestor_ids end |
#siblings ⇒ Object
Siblings
248 249 250 |
# File 'lib/ancestry/instance_methods.rb', line 248 def siblings self.class.ancestry_base_class.siblings_of(self).where.not(self.class.primary_key => id) end |
#subtree(depth_options = {}) ⇒ Object
Subtree
300 301 302 |
# File 'lib/ancestry/instance_methods.rb', line 300 def subtree( = {}) self.class.ancestry_base_class.ordered_by_ancestry.scope_depth(, depth).subtree_of(self) end |
#subtree_ids(depth_options = {}) ⇒ Object
304 305 306 |
# File 'lib/ancestry/instance_methods.rb', line 304 def subtree_ids( = {}) subtree().pluck(self.class.primary_key) end |
#touch_ancestors_callback ⇒ Object
Touch each of this record’s ancestors (after save)
67 68 69 70 71 72 73 74 75 76 |
# File 'lib/ancestry/instance_methods.rb', line 67 def touch_ancestors_callback if !ancestry_callbacks_disabled? # Touch each of the old *and* new ancestors unscoped_current_and_previous_ancestors.each do |ancestor| ancestor.without_ancestry_callbacks do ancestor.touch end end end end |
#update_descendants_with_new_ancestry ⇒ Object
Update descendants with new ancestry (after update)
11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/ancestry/instance_methods.rb', line 11 def update_descendants_with_new_ancestry # If enabled and the new ancestry is sane ... # The only way the ancestry could be bad is via `update_attribute` with a bad value if !ancestry_callbacks_disabled? && sane_ancestor_ids? # ... for each descendant ... unscoped_descendants_before_last_save.each do |descendant| # ... replace old ancestry with new ancestry descendant.without_ancestry_callbacks do new_ancestor_ids = path_ids + (descendant.ancestor_ids - path_ids_before_last_save) descendant.update_attribute(:ancestor_ids, new_ancestor_ids) end end end end |
#update_parent_counter_cache ⇒ Object
95 96 97 98 99 100 101 102 103 |
# File 'lib/ancestry/instance_methods.rb', line 95 def update_parent_counter_cache return unless saved_change_to_attribute?(self.class.ancestry_column) if (parent_id_was = parent_id_before_last_save) self.class.ancestry_base_class.decrement_counter counter_cache_column, parent_id_was end parent_id && increase_parent_counter_cache end |
#without_ancestry_callbacks ⇒ Object
Callback disabling
314 315 316 317 318 319 |
# File 'lib/ancestry/instance_methods.rb', line 314 def without_ancestry_callbacks @disable_ancestry_callbacks = true yield ensure @disable_ancestry_callbacks = false end |