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 ⇒ Object
Apply orphan strategy (before destroy - no changes).
- #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?)
-
#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
NOTE: includes self.
- #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
152 153 154 |
# File 'lib/ancestry/instance_methods.rb', line 152 def ancestor_of?(node) node.ancestor_ids.include?(self.id) end |
#ancestors(depth_options = {}) ⇒ Object
123 124 125 126 |
# File 'lib/ancestry/instance_methods.rb', line 123 def ancestors = {} return self.ancestry_base_class.none unless has_parent? self.ancestry_base_class.scope_depth(, depth).ordered_by_ancestry.ancestors_of(self) end |
#ancestry_callbacks_disabled? ⇒ Boolean
304 305 306 |
# File 'lib/ancestry/instance_methods.rb', line 304 def ancestry_callbacks_disabled? defined?(@disable_ancestry_callbacks) && @disable_ancestry_callbacks end |
#ancestry_changed? ⇒ Boolean
99 100 101 102 103 104 |
# File 'lib/ancestry/instance_methods.rb', line 99 def ancestry_changed? column = self.ancestry_base_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
4 5 6 |
# File 'lib/ancestry/instance_methods.rb', line 4 def ancestry_exclude_self errors.add(:base, I18n.t("ancestry.exclude_self", class_name: self.class.name.humanize)) if ancestor_ids.include? self.id end |
#apply_orphan_strategy ⇒ Object
Apply orphan strategy (before destroy - no changes)
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/ancestry/instance_methods.rb', line 24 def apply_orphan_strategy if !ancestry_callbacks_disabled? && !new_record? case self.ancestry_base_class.orphan_strategy when :rootify # make all children root if orphan strategy is rootify unscoped_descendants.each do |descendant| descendant.without_ancestry_callbacks do descendant.update_attribute :ancestor_ids, descendant.ancestor_ids - path_ids end end when :destroy # destroy all descendants if orphan strategy is destroy unscoped_descendants.each do |descendant| descendant.without_ancestry_callbacks do descendant.destroy end end when :adopt # make child elements of this node, child of its parent descendants.each do |descendant| descendant.without_ancestry_callbacks do descendant.update_attribute :ancestor_ids, (descendant.ancestor_ids.delete_if { |x| x == self.id }) end end when :restrict # throw an exception if it has children raise Ancestry::AncestryException.new(I18n.t("ancestry.cannot_delete_descendants")) unless is_childless? end end end |
#cache_depth ⇒ Object
148 149 150 |
# File 'lib/ancestry/instance_methods.rb', line 148 def cache_depth write_attribute self.ancestry_base_class.depth_cache_column, depth end |
#child_ids ⇒ Object
214 215 216 |
# File 'lib/ancestry/instance_methods.rb', line 214 def child_ids children.pluck(self.ancestry_base_class.primary_key) end |
#child_of?(node) ⇒ Boolean
228 229 230 |
# File 'lib/ancestry/instance_methods.rb', line 228 def child_of?(node) self.parent_id == node.id end |
#children ⇒ Object
Children
210 211 212 |
# File 'lib/ancestry/instance_methods.rb', line 210 def children self.ancestry_base_class.children_of(self) end |
#decrease_parent_counter_cache ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/ancestry/instance_methods.rb', line 68 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.ancestry_base_class.decrement_counter counter_cache_column, parent_id end |
#depth ⇒ Object
144 145 146 |
# File 'lib/ancestry/instance_methods.rb', line 144 def depth ancestor_ids.size end |
#descendant_ids(depth_options = {}) ⇒ Object
263 264 265 |
# File 'lib/ancestry/instance_methods.rb', line 263 def descendant_ids = {} descendants().pluck(self.ancestry_base_class.primary_key) end |
#descendant_of?(node) ⇒ Boolean
267 268 269 |
# File 'lib/ancestry/instance_methods.rb', line 267 def descendant_of?(node) ancestor_ids.include?(node.id) end |
#descendants(depth_options = {}) ⇒ Object
Descendants
259 260 261 |
# File 'lib/ancestry/instance_methods.rb', line 259 def descendants = {} self.ancestry_base_class.ordered_by_ancestry.scope_depth(, depth).descendants_of(self) end |
#has_children? ⇒ Boolean Also known as: children?
218 219 220 |
# File 'lib/ancestry/instance_methods.rb', line 218 def has_children? self.children.exists? end |
#has_parent? ⇒ Boolean Also known as: ancestors?
Ancestors
94 95 96 |
# File 'lib/ancestry/instance_methods.rb', line 94 def has_parent? ancestor_ids.present? end |
#has_siblings? ⇒ Boolean Also known as: siblings?
243 244 245 |
# File 'lib/ancestry/instance_methods.rb', line 243 def has_siblings? self.siblings.count > 1 end |
#increase_parent_counter_cache ⇒ Object
Counter Cache
64 65 66 |
# File 'lib/ancestry/instance_methods.rb', line 64 def increase_parent_counter_cache self.ancestry_base_class.increment_counter counter_cache_column, parent_id end |
#indirect_ids(depth_options = {}) ⇒ Object
277 278 279 |
# File 'lib/ancestry/instance_methods.rb', line 277 def indirect_ids = {} indirects().pluck(self.ancestry_base_class.primary_key) end |
#indirect_of?(node) ⇒ Boolean
281 282 283 |
# File 'lib/ancestry/instance_methods.rb', line 281 def indirect_of?(node) ancestor_ids[0..-2].include?(node.id) end |
#indirects(depth_options = {}) ⇒ Object
Indirects
273 274 275 |
# File 'lib/ancestry/instance_methods.rb', line 273 def indirects = {} self.ancestry_base_class.ordered_by_ancestry.scope_depth(, depth).indirects_of(self) end |
#is_childless? ⇒ Boolean Also known as: childless?
223 224 225 |
# File 'lib/ancestry/instance_methods.rb', line 223 def is_childless? !has_children? end |
#is_only_child? ⇒ Boolean Also known as: only_child?
248 249 250 |
# File 'lib/ancestry/instance_methods.rb', line 248 def is_only_child? !has_siblings? end |
#is_root? ⇒ Boolean Also known as: root?
199 200 201 |
# File 'lib/ancestry/instance_methods.rb', line 199 def is_root? !has_parent? end |
#parent ⇒ Object
173 174 175 176 177 178 179 |
# File 'lib/ancestry/instance_methods.rb', line 173 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
160 161 162 |
# File 'lib/ancestry/instance_methods.rb', line 160 def parent= parent self.ancestor_ids = parent ? parent.path_ids : [] end |
#parent_id ⇒ Object
168 169 170 |
# File 'lib/ancestry/instance_methods.rb', line 168 def parent_id ancestor_ids.last if has_parent? end |
#parent_id=(new_parent_id) ⇒ Object
164 165 166 |
# File 'lib/ancestry/instance_methods.rb', line 164 def parent_id= new_parent_id self.parent = new_parent_id.present? ? unscoped_find(new_parent_id) : nil end |
#parent_of?(node) ⇒ Boolean
181 182 183 |
# File 'lib/ancestry/instance_methods.rb', line 181 def parent_of?(node) self.id == node.parent_id end |
#path(depth_options = {}) ⇒ Object
140 141 142 |
# File 'lib/ancestry/instance_methods.rb', line 140 def path = {} self.ancestry_base_class.scope_depth(, depth).ordered_by_ancestry.inpath_of(self) end |
#path_ids ⇒ Object
128 129 130 |
# File 'lib/ancestry/instance_methods.rb', line 128 def path_ids ancestor_ids + [id] end |
#path_ids_before_last_save ⇒ Object
132 133 134 |
# File 'lib/ancestry/instance_methods.rb', line 132 def path_ids_before_last_save ancestor_ids_before_last_save + [id] end |
#path_ids_in_database ⇒ Object
136 137 138 |
# File 'lib/ancestry/instance_methods.rb', line 136 def path_ids_in_database ancestor_ids_in_database + [id] end |
#root ⇒ Object
191 192 193 194 195 196 197 |
# File 'lib/ancestry/instance_methods.rb', line 191 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
187 188 189 |
# File 'lib/ancestry/instance_methods.rb', line 187 def root_id has_parent? ? ancestor_ids.first : id end |
#root_of?(node) ⇒ Boolean
204 205 206 |
# File 'lib/ancestry/instance_methods.rb', line 204 def root_of?(node) self.id == node.root_id end |
#sane_ancestor_ids? ⇒ Boolean
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/ancestry/instance_methods.rb', line 106 def sane_ancestor_ids? current_context, self.validation_context = validation_context, nil errors.clear attribute = ancestry_base_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
NOTE: includes self
239 240 241 |
# File 'lib/ancestry/instance_methods.rb', line 239 def sibling_ids siblings.pluck(self.ancestry_base_class.primary_key) end |
#sibling_of?(node) ⇒ Boolean
253 254 255 |
# File 'lib/ancestry/instance_methods.rb', line 253 def sibling_of?(node) self.ancestor_ids == node.ancestor_ids end |
#siblings ⇒ Object
Siblings
234 235 236 |
# File 'lib/ancestry/instance_methods.rb', line 234 def siblings self.ancestry_base_class.siblings_of(self) end |
#subtree(depth_options = {}) ⇒ Object
Subtree
287 288 289 |
# File 'lib/ancestry/instance_methods.rb', line 287 def subtree = {} self.ancestry_base_class.ordered_by_ancestry.scope_depth(, depth).subtree_of(self) end |
#subtree_ids(depth_options = {}) ⇒ Object
291 292 293 |
# File 'lib/ancestry/instance_methods.rb', line 291 def subtree_ids = {} subtree().pluck(self.ancestry_base_class.primary_key) end |
#touch_ancestors_callback ⇒ Object
Touch each of this record’s ancestors (after save)
52 53 54 55 56 57 58 59 60 61 |
# File 'lib/ancestry/instance_methods.rb', line 52 def touch_ancestors_callback if !ancestry_callbacks_disabled? && self.ancestry_base_class.touch_ancestors # 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)
9 10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/ancestry/instance_methods.rb', line 9 def update_descendants_with_new_ancestry # If enabled and node is existing and ancestry was updated and the new ancestry is sane ... if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestor_ids? # ... for each descendant ... unscoped_descendants_before_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
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/ancestry/instance_methods.rb', line 80 def update_parent_counter_cache changed = saved_change_to_attribute?(self.ancestry_base_class.ancestry_column) return unless changed if parent_id_was = parent_id_before_last_save self.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
297 298 299 300 301 302 |
# File 'lib/ancestry/instance_methods.rb', line 297 def without_ancestry_callbacks @disable_ancestry_callbacks = true yield ensure @disable_ancestry_callbacks = false end |